Failure to load properly Groovy library into Groovy shell executing script - java

My software used groovy.lang Java package to execute Groovy scripts from a shell, binding the variables in the script to Java objects.
A typical script looks like:
package packagename
// import Java classes
abstract class MyClass extends Script {
def myfunction() {
}
}
in this example, 'myfunction' will be called from the outside.
The scripts (located at the file system) are loaded by the following sequence from Java -
the code returns GroovyShell class instance:
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(...)
File groovyFile = new File(groovyURL.toURI());
Class<?> groovyClass = groovyClassLoader.parseClass(groovyFile);
CompilerConfiguration groovyConfig = new CompilerConfiguration();
groovyConfig.setScriptBaseClass(groovyClass.getName());
return new GroovyShell(groovyClassLoader, new Binding(), groovyConfig);
My design goal is to add a Groovy library that can be shared between scripts
My preference is to implement a class (adding lines into the existing script seems to be a hack).
I made a simple class representing the library code. Right now, it looks like:
package shared
class MySharedLib
{
static def testFunction()
{
return "test";
}
}
To make sure the class it loaded, I added a call to
groovyClassLoader.parseClass(groovyLibraryFile)
before loading the actual script by:
groovyClassLoader.parseClass(groovyFile);
Now, from the script, I can call the library:
shared.MySharedLib.testFunction()
indeed return the string "test".
However, when trying to do the import via:
import shared.MySharedLib
in the script (before class definition) - I always got an error when loading the script:
Exception in thread "main" org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
script754084858.groovy: 14: unable to resolve class shared.MySharedLib
# line 14, column 1.
Tried to modify the classpath, it did not help. I realize something is wrong with my setup.
Will appreciate any tip how to load a Groovy library in the correct way.
Max

Thanks for comments.
I think I understand the cause.
It turns out that at some point, the "script" is being compiled using
GroovyClassLoader classLoader = new GroovyClassLoader(parentClassLoader);
GroovyCodeSource codeSource = new GroovyCodeSource(code, scriptClassName + ".groovy", "/groovy/script");
CompilationUnit cu = new CompilationUnit(classLoader);
cu.addSource(codeSource.getName(), codeSource.getScriptText());
cu.compile(CompilePhase.CLASS_GENERATION.getPhaseNumber());
The compilation fails when reaching the "import" statement, since the library class is not in the classpath, so it's unreachable.
Calling classLoader.addClasspath(path) with the appropriate path solves the issue.
So the problem was related to compilation - not to execution.

Related

Running a Python program in Java using Jython

I wrote a Python program that consists out of five .py script files.
I want to execute the main of those python scripts from within a Java Application.
What are my options to do so? Using the PythonInterpreter doesn't work, as for example the datetime module can't be loaded from Jython (and I don't want the user to determine his Python path for those dependencies to work).
I compiled the whole folder to .class files using Jython's compileall. Can I embed these .class files somehow to execute the main file from within my Java Application, or how should I proceed?
Have a look at the ProcessBuilder class in java: https://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html.
The command used in the java constructor should be the same as what you would type in a command line. For example:
Process p = new ProcessBuilder("python", "myScript.py", "firstargument").start();
(the process builder does the same thing as the python subprocess module).
Have a look at running scripts through processbuilder
N.B. as for the Jython part of the question, if you go to the jython website (have a look at the FAQ section of their website www.jython.org). Check the entry "use jython from java".
I'm also interested in running Python code directly within Java, using Jython, and avoiding the need for an installed Python interpreter.
The article, 'Embedding Jython in Java Applications' explains how to reference an external *.py Python script, and pass it argument parameters, no installed Python interpreter necessary:
#pymodule.py - make this file accessible to your Java code
def square(value):
return value*value
This function can then be executed either by creating a string that
executes it, or by retrieving a pointer to the function and calling
its call method with the correct parameters:
//Java code implementing Jython and calling pymodule.py
import org.python.util.PythonInterpreter;
import org.python.core.*;
public class ImportExample {
public static void main(String [] args) throws PyException
{
PythonInterpreter pi = new PythonInterpreter();
pi.exec("from pymodule import square");
pi.set("integer", new PyInteger(42));
pi.exec("result = square(integer)");
pi.exec("print(result)");
PyInteger result = (PyInteger)pi.get("result");
System.out.println("result: "+ result.asInt());
PyFunction pf = (PyFunction)pi.get("square");
System.out.println(pf.__call__(new PyInteger(5)));
}
}
Jython's Maven/Gradle/etc dependency strings can be found at http://mvnrepository.com/artifact/org.python/jython-standalone/2.7.1
Jython JavaDoc
It is possible to load the other modules. You just need to specify the python path where your custom modules can be found. See the following test case and I am using the Python datatime/math modules inside my calling function (my_maths()) and I have multiple python files in the python.path which are imported by the main.py
#Test
public void testJython() {
Properties properties = System.getProperties();
properties.put("python.path", ".\\src\\test\\resources");
PythonInterpreter.initialize(System.getProperties(), properties, new String[0]);
PythonInterpreter interpreter = new PythonInterpreter();
interpreter.execfile(".\\src\\test\\resources\\main.py");
interpreter.set("id", 150); //set variable value
interpreter.exec("val = my_maths(id)"); //the calling function in main.py
Integer returnVal = (Integer) interpreter.eval("val").__tojava__(Integer.class);
System.out.println("return from python: " + returnVal);
}

Load Python class through Jython

I'm trying to load a Python class by embedding Jython in my Java application.
The code I have so far is
String pythonRoot = Main.class.getResource("/python").getPath();
PySystemState state = new PySystemState();
PyObject importer = state.getBuiltins().__getitem__(Py.newString("__import__"));
PyObject sysModule = importer.__call__(Py.newString("sys"));
final PyString pythonPath = Py.newString(pythonRoot);
PyList path = (PyList) sysModule.__getattr__("path");
path.add(pythonPath);
PyModule module = (PyModule) importer.__call__(Py.newString("building.blah.again.blah2.Test"));
PyObject klass = module.__getattr__(Py.newString("Address"));
AddressInterface ai = (AddressInterface) klass.__call__().__tojava__(AddressInterface.class);
The class I'm trying to access can be found in
/python/building/blah/again/blah2/Test.py
And the name of the class is
Address
However, this gives me the exception
Exception in thread "main" ImportError: No module named blah2
If I place some file in the directory above, like so
/python/building/blah/again/Test.py
This gives me the exception
Exception in thread "main" ImportError: No module named again
It's as if he is actively refusing to recognize directories that contains files. What can be the problem here and how might I proceed to get around this?
If you added the path of your module to the Python-path, which you did via path.add(pythonPath);, the import-command expects only the name of the module, not the full path, i.e. PyModule module = (PyModule) importer.__call__(Py.newString("Test"));
Further, you should confirm that actually the right path was added by printing the contents of the path-list. Also note that your class-declaration in Test.py must extend the Address-java-interface for toJava to work (I just mention this because this is also a common source of error).
That said, your way of using Jython appears somewhat cumbersome to me. If I were you, I would do this stuff (adding the path, doing the import) in a python-script, run it via org.python.util.PythonInterpreter (http://www.jython.org/javadoc/org/python/util/PythonInterpreter.html) and retrieve the class-PyObject via the eval or get-method.

Serializing a JRuby CompiledScript in Java

I have a Ruby script that I'd like to run at the startup of my Java program.
When you tell the ScriptEngine to evaluate the code for the first time, it takes a while. I'm under the impression that the reason it takes this long is because it first needs to compile the code, right?
I found that you can compile Ruby code, and then evaluate it later. The evaluation itself is fast - the compilation part is the slow one. Here I am compiling:
jruby = new ScriptEngineManager().getEngineByName("jruby");
Compilable compilingEngine = (Compilable)jruby;
String code = "print 'HELLO!'";
CompiledScript script;
script = compilingEngine.compile(code);
This snippet is what takes a while. Later when you evaluate it, it is fine.
So of course, I was wondering if it would be possible to "save" this compiled code into a file, so in the future I can "load" it and just execute it without compiling again.
As others have said, this is not possible with CompiledScript. However, with JRuby you have another option. You can use the command line tool jrubyc to compile a Ruby script to Java bytecode like so:
jrubyc <scriptname.rb>
This will produce a class file named scriptname.class. You can run this class from the command line as if it were a normal class with a main(String[] argv) method (note: the jruby runtime needs to be in the classpath) and you can of course load it into your application at runtime.
You can find more details on the output of jrubyc here: https://github.com/jruby/jruby/wiki/JRubyCompiler#methods-in-output-class-file
According to this, no.
"Unfortunately, compiled scripts are not, by default, serializable, so they can't be pre-compiled as part of a deployment process, so compilation should be applied at runtime when you know it makes sense."
I think some really easy cache will solve your problem:
class CompiledScriptCache {
static {
CompiledScriptCache INSTANCE = new CompiledScritCache();
}
publich static CompiledScriptCache get(){
retrun INSTANCE;
};
List<CompiledScript> scripts = new ArrayList<>();
public CompiledScript get(int id){
return scripts.get(id);
}
public int add(String code){
ScriptEngine jruby = new ScriptEngineManager().getEngineByName("jruby");
Compilable compilingEngine = (Compilable)jruby;
CompiledScript script;
script = compilingEngine.compile(code);
scripts.add(script);
return scripts.size()-1;
}
}
update
I thought this question was about avoiding to comile the source more than once.
Only other approach I could imagine is to create Java-Classes and make a cross-compile:
https://github.com/jruby/jruby/wiki/GeneratingJavaClasses

Can we Runtime.getRuntime().exec("groovy");

I installed Groovy.
And I am trying to run groovy scripts from a command prompt that I created using Java, like so:
Runtime.getRuntime().exec("groovy");
So if I type in "groovy" to the command line, this is what I get:
>>>groovy
Cannot run program "groovy": CreateProcess error=2, The system cannot find the file specified
Does anyone have an idea as to what might be going wrong? Should I just use Groovy's implementation of exec? Like:
def processBuilder=new ProcessBuilder("ls")
processBuilder.redirectErrorStream(true)
processBuilder.directory(new File("Your Working dir")) // <--
def process = processBuilder.start()
My guess is that it wouldn't matter whether using Java's implementation or Groovy's implementation.
So how do I run a groovy script?
The way originally described in the question above calling the groovy executable invokes a second Java runtime instance and class loader while the efficient way is to embed the Groovy script directly into the Java runtime as a Java class and invoke it.
Here are three ways to execute a Groovy script from Java:
1) Simplest way is using GroovyShell:
Here is an example Java main program and target Groovy script to invoke:
== TestShell.java ==
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
// call groovy expressions from Java code
Binding binding = new Binding();
binding.setVariable("input", "world");
GroovyShell shell = new GroovyShell(binding);
Object retVal = shell.evaluate(new File("hello.groovy"));
// prints "hello world"
System.out.println("x=" + binding.getVariable("x")); // 123
System.out.println("return=" + retVal); // okay
== hello.groovy ==
println "Hello $input"
x = 123 // script-scoped variables are available via the GroovyShell
return "ok"
2) Next is to use GroovyClassLoader to parse the script into a class then create an instance of it. This approach treats the Groovy script as a class and invokes methods on it as on any Java class.
GroovyClassLoader gcl = new GroovyClassLoader();
Class clazz = gcl.parseClass(new File("hello.groovy");
Object aScript = clazz.newInstance();
// probably cast the object to an interface and invoke methods on it
3) Finally, you can create GroovyScriptEngine and pass in objects as variables using binding. This runs the Groovy script as a script and passes in input using binding variables as opposed to calling explicit methods with arguments.
Note: This third option is for developers who want to embed groovy scripts into a server and have them reloaded on modification.
import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;
String[] roots = new String[] { "/my/groovy/script/path" };
GroovyScriptEngine gse = new GroovyScriptEngine(roots);
Binding binding = new Binding();
binding.setVariable("input", "world");
gse.run("hello.groovy", binding);
System.out.println(binding.getVariable("output"));
Note: You must include the groovy_all jar in your CLASSPATH for these approaches to work.
Reference: http://groovy.codehaus.org/Embedding+Groovy

Executing groovy file in Java

I am trying to execute a .groovy file in Java however being new to both Java and Groovy I am having some problems. I'm doing this to learn more and would appreciate if someone could tell me what i am doing wrong.
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyShell;
import javax.naming.Binding;
import java.io.File;
public class testClass extends GroovyShell{
public static void main(String[] args){
try{
ClassLoader parent = testClass.class.getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
Class groovyClass = loader.parseClass(new File("src/testg.groovy"));
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
Object[] args1 = {};
groovyObject.invokeMethod("run",args1);
}catch(Exception e) {
System.out.println("error loading file");
}
}
}
I am getting the following errors :
Groovyc: Cannot compile Groovy files: no Groovy library is defined for module 'Prep'
Using javac 1.7.0_09 to compile java sources
Compilation completed with 1 error and 0 warnings in 9 sec
1 error
0 warnings
Groovyc: Internal groovyc error: code 1
Or perhaps someone could give me an example of how to execute e.g. hello world script written in groovy, in java.
That's not an error thrown by your code. It's an error thrown by IntelliJ, which tries to compile your .groovy file to a .class file.
Since what you want is to parse and run this groovy file at runtime, you shouldn't care about this error. Or rather, to avoid it, you should not put the .groovy file in a directory marked as a source directory in the IntelliJ project, so that IntelliJ doesn't try to compile it.

Categories

Resources