I want to use a 'Util' groovy script inside another groovy script. I don't want to load 'Util' class inside my 'main' groovy script every time. So using evaluate or GroovyShell don't fit my case.
My java application fetches the 'main' groovy script body from a database, parse it and call test() method from 'main' script every time.
java code :
GroovyShell groovyShell = new GroovyShell();
Script parsedScript = groovyShell.parse(scriptBody);
ResultPojo result = (ResultPojo) parsedScript.invokeMethod("test", null);
'main' script
public int test(){
// this will not work at the moment
int result = GroovyUtils.sum();
return result;
}
A 'Util' class will be located in the database too. 'Util' classes will be somehow loaded on application startup and they will be reloaded every X minutes.
class GroovyUtils{
static int sum() {
return 2+1;
}
}
Like i said i don't want to 'parse' the GroovyUtils class inside 'main' script because this is time costly.
Ideally i want to import GroovyUtils script when i need it.
import groovy.GroovyUtils;
public int test(){
int result = GroovyUtils.sum();
return result;
}
But in order to import the script, the script need to be saved in the same folder that the java application runs. The java application is deployed on a remote application server in .war format.
Can i somehow load GroovyUtils dynamically to CLASSPATH without saving it, so i can import it from my 'main' script?
Any suggestions? My main concerns is speed and reloadability.
if you'd like to create a delivery process through the database you can do it by extending GroovyClassLoader and implementing public Class loadClass(name, lookupScriptFiles, preferClassOverScript, resolve) method that will search classes in some table in a database.
Let me simplify your goal and exclude database.
There is a standard behavior of classloaders: search and load classes among the classpath
The GroovyClassLoader allows to add new paths to a classpath at runtime, so it will search additionally classes in specified folder or jar file.
classloader keeps parsed classes in memory and groovy classloader provides protected method to remove class definition by name: removeClassCacheEntry(java.lang.String)
and finally example:
/myprj/classes/util/MyClass.groovy
package util
class MyClass{
def echo(msg){ println msg }
}
code to run main script
//create shell and init classloader just once
GroovyShell gs = new GroovyShell()
gs.getClassLoader().addClasspath("/myprj/classes/")
//forces classloader to recompile on file change
//this is alternative to removeClassCacheEntry
//but in some specific cases this reload will not work
gs.getClassLoader().setShouldRecompile​(true)
Script script = gs.parse('''
import util.MyClass
new MyClass().echo("hello world")
''')
script.run() // prints 'hello world'
//removeClassCacheEntry is alternative to setShouldRecompile​
//you can use it to remove compiled class from this classloader
println gs.getClassLoader().getLoadedClasses() // outputs util.MyClass, and Script1
gs.getClassLoader().removeClassCacheEntry("util.MyClass")
println gs.getClassLoader().getLoadedClasses() // outputs Script1
returning to the database: you could have a daemon thread that scans database for groovy code changes and exports modified sources into a folder that was defined as additional classpath and triggers removeClassCacheEntry for the classloader. So, next access to removed class will force to parse it by GroovyClassLoader.
NOTE: by using dynamic class loading you could have situation when two versions of same class present in memory and they will not be comparible and assignable to each other. So, you could have the error like: could not assign MyClass to MyClass
Related
1). I know how to access the java fields and object in beanshell from my question Use java class fields in beanshell. However, it is not so clean way to implement as I need to first set the java variable in beanshell and then I can use it. However, in Jmeter it provides very clean way of using maps in beanshell similar way as we do in java, but JMeter has developed it's know library (class) which helps to access get/put methods for maps. I want to achieve similar way to access Map in beanshell.
I have checked JMeter for more information and I want to know that, I have created user define variable temp and assign value error, now in BSF process I just write a line vars.put('Name','temp Value') and it has updated value for temp variable. So, the question is I have not created JMeterVariables object vars but still beanshell allows to update values in map without setting any values as mention in your answer. I want to know how this works, need more depth information.
2). I have created my own class in java and in beanshell I am importing this class but it is giving Command not found: BSClass() below is the entire code
Java class
package test;
public class BSClass {
public void BSCMethod(){
System.out.println("I am from BSClass method BSCMethod");
}
}
sample.bsh
import test.BSClass;
c=BSClass();
c.BSCMethod();
print("I am from BeanShell Script");
Calling sample.bsh file java class
package test;
import java.io.FileNotFoundException;
import java.io.IOException;
import bsh.*;
public class DynamicVariable {
public static void main(String[] args) throws FileNotFoundException, IOException, EvalError {
new bsh.Interpreter().source("\\src\\test\\sample.bsh");
}
}
Note:
I don't need help in JMeter, it is to use in core java and beanshell.
All the files are in my project.
BSClass.class is under bin folder of my project
I would appreciate your inputs
In Beanshell you can add any Object you want including a Map
In JMeter, JMeterVariables is special implementation of Map that is added to Beanshell Interpreter before evaluate and also special Object as JMeterContext is added which even includes JMeterVariables inside. Code:
JMeterContext jmctx = JMeterContextService.getContext();
JMeterVariables vars = jmctx.getVariables();
try {
bshInterpreter.set("ctx", jmctx);//$NON-NLS-1$
bshInterpreter.set("Label", getName()); //$NON-NLS-1$
bshInterpreter.set("prev", jmctx.getPreviousResult());//$NON-NLS-1$
bshInterpreter.set("props", JMeterUtils.getJMeterProperties());
bshInterpreter.set("vars", vars);//$NON-NLS-1$
In your case with map you can do similar as you describe in comment:
bshInterpreter.set("myMap", javaMyMapObject);"
Then in Beanshell get the specific key from map:
myMap.get("aField");
To create class you should use new keyword, call:
c= new BSClass();
instead of c=BSClass();
If you create your own class, Class should be inside jar in relevant package .
The jar should be located in lib folder and not in bin folder, see JMeter's getting started:
Any jar file in such a directory will be automatically included in
user.classpath, jar files in sub directories are ignored. The given
value is in addition to any jars found in the lib directory. All
entries will be added to the class path of the system class loader and
also to the path of the JMeter internal loader.
I'm trying to run Groovy scripts inside my Java Application using the GroovyClassLoader and the GroovyScriptEngineImpl, and I want to isolate the Groovy script from the parent application context.
What I mean is that when I'm running my Groovy script, I don't want it to inherit from the dependencies loaded in my Java application in order to be able to, for example, load Gson 2.5.5 in my script, even if my Java application is using Gson 3.4.1.
// Replacing the context class loader with the system one
ClassLoader initialCL = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader().getParent());
// Creating my GroovyClassLoader and GroovyScriptEngine
GroovyClassLoader groovyCL = new GroovyClassLoader();
GroovyScriptEngineImpl scriptEngine = new GroovyScriptEngineImpl(groovyCL);
// Compiling and running my Groovy script
CompiledScript compiledScript = scriptEngine.compile("println \"hello\"");
compiledScript.eval();
// Going back to my initial classloader
Thread.currentThread().setContextClassLoader(initialCL);
This way, the isolation is indeed working, but the content of the script is not executed at all (even printing a line in the console for example), and I'm getting no error anywhere.
If I don't update my context classloader before creating the new GroovyClassLoader, the script is working fine, but it's inheriting from the parent dependencies.
Do you have any idea?
Thanks :)
UPDATE : After a bit more testing, It seems like the compilation is working properly, but the evaluation of the compiled script isn't doing anything. Indeed, I'm getting an error when trying to compile a script that doesn't have all the dependencies needed even tho they're present in my Java application classpath.
Okay, finally figured it out, and this is an interesting one.
I said that there was no error, and while it wasn't printed, I actually had one (quite obvious since it wasn't working...). I managed to get the error by changing slightly my method to execute Groovy scripts using a GroovyShell instead of my GroovyScriptEngineImpl and GroovyClassLoader:
Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader().getParent());
GroovyClassLoader groovyCL = new GroovyClassLoader();
new GroovyShell(groovyCL).evaluate("println(\"test\")");
And here's the error I'm finally getting and that was, for some reason, hidden during my previous executions, not using the GroovyShell:
groovy.lang.GroovyRuntimeException: Failed to create Script instance for class: class Script1. Reason: java.lang.ClassCastException: Script1 cannot be cast to groovy.lang.GroovyObject
So what's the problem?
Well actually, the classloader that will be used to compile the script and the one used to evaluate the compiled script are not the same: groovyCL is used to compile the script, and the "current thread" classloader is used to create the GroovyShell object and evaluate the script.
This means that the groovy.lang.GroovyObject from both classloaders are not compatible, since the class is defined in both classloaders (even tho they're the exact same class codewise).
Then, when trying to cast the Script1 object created by groovyCL, the GroovyShell (or the mechanism I used before with GroovyScriptEngineImpl etc...) will encounter ClassCastException.
This whole thing can lead to funny errors such as:
java.lang.ClassCastException: groovy.lang.GroovyShell cannot be cast to groovy.lang.GroovyShell
The solution
So, what you want to do is to create your GroovyClassLoader instance, and, using this classloader, create all the objects of the workflow using reflection:
GroovyClassLoader groovyCL = new GroovyClassLoader();
Class groovyShellClazz = groovyCL.loadClass(GroovyShell.class.getName());
Object groovyShellObj = groovyShellClazz.newInstance();
Method evaluateMethod = groovyShellClazz.getMethod("evaluate", String.class);
evaluateMethod.invoke(groovyShellObj, "println(\"test\")");
This requires a bit more work, but the script will be compiled and evaluated by the same classloader and the problem will be fixed.
I still have to work on adapting this solution to my initial situation using the GroovyScriptEngineImpl but it's the exact same method :)
I'm trying to figure out how to load two co-dependent Groovy scripts in java at runtime. If I have two groovy scripts like:
A.groovy
import B
class A {
A() {
B b = new B()
}
}
B.groovy
import A
class B {
B() {
A a = new A()
}
}
I would like to load them as java classes, but when I run:
ClassLoader parent = getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
loader.parseClass(new File("A.groovy"));
I get the error:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
A.groovy: 1: unable to resolve class B
# line 1, column 1.
import B
I certainly understand the reason for the error, but is there any way to load these classes at runtime despite their co-dependency? Thanks!
GroovyClassLoader must be enabled to find B.groovy on the classpath. Normally that means you change the classpath of you application to include the root of the scripts. Since there is no package here for the scripts and since you use new File("A.groovy"), I would assume that it is here the current directory.
If you don't want to change the classpath of the application, you can also call addURL to add the path containing the scripts.
One more thing to mention.... parseClass will always create a newly parsed class. You might want to try a standard loadClass call instead to avoid compiling the file multiple times. But of course that works only after you fixed the lookup for GroovyClassLoader, because using loadClass, GroovyClassLoader will also have to look for A.groovy in the same manner it does have to look for B.groovy
Let us have a Groovy/Java application that should use a set of classes, defined in external *.jar-files (suppose they are located near the main executable jar).
So, the main class (let us call it Main) should load plugin.jar file at runtime and call some instance method on the class, defined in that jar (for some convention, suppose the class has the name as its jar - Plugin in our case).
The Main class could not know which plugins it has until it is runned. Let's throw away the CLASSPATH and java -jar run arguments and just do the magic with code only.
So, how this could be done and how the plugin.jar should be created (using Eclipse in my case) in order to be correctly loaded?
PS: yeah, i do compile my groovy sources into jar file. But i need to perform class loading and invoke exactly on-the-fly.
The secret was really simple!
Using URLClassLoader does the trick.
So, Groovy code:
ClassLoader loader = new URLClassLoader((URL[]) [
new File("C:\\Users\\errorist\\workspace\\javatest1\\bin\\").toURI().toURL()
])
Class c = loader.loadClass("src.SomeClass1")
c.invokeMethod("main", (String[]) ["Hello", "World"])
And the Java one:
File file = new File("C:\\Users\\errorist\\workspace\\javatest1\\bin\\");
URL[] urls = new URL[] { file.toURI().toURL() };
ClassLoader loader = new URLClassLoader(urls);
Class c = loader.loadClass("src.SomeClass1");
c.invokeMethod("main", new String[] { "Hello", "World!" });
The OSGi framework supports dynamic loading of plug-ins. There are multiple implementations, including Equinox, which underpins Eclipse itself.
I have 204 total classes (most of the classes are inner classes). For months, I have been building fine with SCons (SCons just calls the jar command).
For some reason, it stopped adding the last inner class for a particular class. For example, suppose I have the following classes:
class1
class2
class3
class4
class5
class6
...
class79
class80
Before this last change, SCons would jar everything fine. But NOW... it specifically does not add class80 to it's jar command. (I see an omission of the class80 in the jar command).
Is there an instance where the jar command just ignores certain classes?
----------- EDIT. I found the culprit. For some reason this inner class is not recognized my SCons!
vehicleFilter = new RowFilter<Object, Object>(){
public boolean include(Entry<? extends Object, ? extends Object> entry) {
{return false;}
};
You need to add JAVAVERSION='1.6' as an argument to your env.Java() call:
env.Java(target='classes', source='src', JAVAVERSION='1.6')
Without this, if you're compiling with a current javac, SCons won't determine the correct names for anonymous inner classes, so when those bad class file names get passed to jar, it will fail.
Rather than pass a whole list of class files to the Jar command, you can pass a directory. This avoids problems with SCons's java parser as SCons will scan the directory for files and jar up anything it finds.
Something like the following will compile files in "src" to the directory "classes", then create a jar from the contents of "classes":
env = Environment(tools=['javac', 'jar'])
env.Java(target='classes', source='src')
env.Jar(target='foo.jar', source=['classes', 'Manifest.txt'],
JARCHDIR='$SOURCE')
The manifest file "Manifest.txt" is in the root of your project here. The only requirement is that it begins with the text "Manifest-Version".
SCons may construct a command line by listing all classes to jar on it and that may get too long (either a platform limitation or a heuristic inside SCons).
You need to peek inside the SCons package to see what goes on.
Any particular reason you don't just use ant?