How can I use wrapped Groovy shell inside the groovy script? - groovy

I have a Java app through which I'm executing a Groovy Script, the problem occurs when my Script again has code to use the groovy shell to execute the Inner script.
something like this
import com.xx.WrappedGroovyShell
import java.lang.*
def scriptString = """
def test(str) {
str.toLowerCase() // This doesn't work as GString can't seem to be treated as a String
}
"""
try {
def script = WrappedGroovyShell.getInstance().getScript("Test1", scriptString)
def script2 = new GroovyShell().parse(scriptString)
def example = 1
def gstring = "OUR VALUE IS ${example}";
println script instanceof GroovyObject // Statement returning false
println script.test(gstring) // This throws an exception groovy.lang.MissingMethodException: No signature of method: org.codehaus.groovy.runtime.GStringImpl.toLowerCase() is applicable for argument types: () values: []
println script2 instanceof GroovyObject // Statement returns true
println script2.test(gstring) //this one works
println "Success"
} catch (ex) {
println ex
}
return 0
The getScript method in WrappedGroovyShell.java is as simple as
public Script getScript(String scriptName, String scriptBody){
return new GroovyShell().parse(scriptBody);
}
I will be thankful if someone let me know why two different behavior.

new GroovyShell() creates a new groovy classloader with parent = GroovyShell.class.getClassLoader()
and if GString.class is unknown for parent classloader - then it will be loaded again.
two GString classes loaded by different classloaders are not equal that's why you could have unexpected exceptions like "method not found for parameter GString" or "can't cast GString to GString"
this code should work because it uses classloader of current groovy class
def scriptText='''
def test(str) {
str.toLowerCase()
}
'''
def gs = new GroovyShell(this.getClass().getClassLoader())
def script = gs.parse(scriptText)
def arg = "GSTRING ${123}"
println script.test(arg)

Related

Groovy access script variable from application

Need to access the variable defined in the groovy script file
Before executing the script using GroovyShell
However, I am getting error:
groovy.lang.MissingPropertyException: No such property: _THREADS for class: Test1
//Test1.groovy
_THREADS=10;
println("Hello from the script test ")
//scriptRunner.groovy
File scriptFile = new File("Test1.groovy");
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
def script = shell.parse (scriptFile);
def thread = script.getProperty('_THREADS'); // getting ERROR --
// No such property: _THREADS for class: Test1
//-------
//now run the threads
script.run()
you have to run script before getting property/binding
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
def script = shell.parse (''' _THREADS=10 ''')
script.run()
println script.getProperty('_THREADS')
try to open AST Browser (Ctrl+T) in groovy console for this code:
_THREADS=10
and you'll see approximately following generated class:
public class script1663101205410 extends groovy.lang.Script {
public java.lang.Object run() {
_THREADS = 10
}
}
where _THREADS = 10 assigns property into binding
however it's possible to define _THREADS as a function then it will be accessible without running script.
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
def script = shell.parse (''' def get_THREADS(){ 11 } ''')
println script.getProperty('_THREADS')
got the solution using #Field modifier value can be accessed after parsing
import groovy.transform.Field
#Field _THREADS=10
File scriptFile = new File("Test1.groovy");
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
def script = shell.parse (scriptFile);
def thread = script.getProperty('_THREADS'); //value is accessible

Skip MissingPropertyException while using GroovyShell.evaluate

Is there a way to skip MissingPropertyException while using GroovyShell.evaluate?
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
shell.evaluate("a=5; b=1") // works fine
// How to not get MissingPropertyException, or silently ignore property 'a'
shell.evaluate("a; b=1") // MissingPropertyException for 'a'
I know about the Expando solution, is there a way to do it without defining a class?
A very minimal approach would be to override Binding.getVariable.
Note that this is very straight forward: "all exceptions" are ignored - you might want to have better logging or more accurate error handling.
import groovy.lang.*
class NoOpBinding extends Binding {
#Override
Object getVariable(String name) {
try {
return super.getVariable(name)
}
catch (Throwable t) {
println "Ignoring variable=`$name`"
return null
}
}
}
def shell = new GroovyShell(new NoOpBinding())
shell.evaluate("a; b=1") // MissingPropertyException for 'a'
// → Ignoring variable=`a`
println shell.getVariable('b')
// → 1
You could do something like this:
def binding = [:].withDefault { }
def shell = new GroovyShell(binding as Binding)
shell.evaluate 'a; b=1'

Not able to read the system variable set from GebConfig.groovy file in spec file

I am using geb spock. I am trying to read the system variable from GebConfig file, however it returns the null value.
Below is my GebConfig.groovy file.
def final DEFAULT_BROWSER = "chrome"
def final DEFAULT_LANGUAGE = "nl" //"en" or "nl"
def browser = System.getProperty("geb.env")
//Default browser
if (!correctBrowser(browser)) {
browser = DEFAULT_BROWSER
}
def envLang = System.getProperty("geb.env.lang")
//Default language
if (!correctLanguage(envLang)) {
envLang = DEFAULT_LANGUAGE
}
System.setProperty("geb.env.lang", envLang)
System.setProperty("geb.env", browser)
environments {
driver = { getDriver(browser, envLang) }
}
Below is my spec file where I am trying to get the language value in a variable.
#Stepwise
class TC001_SMO_Scenario_Spec extends GebReportingSpec {
#Shared
def lang = System.getProperty("geb.env.lang")
def "Step 1: Perform Login"() {
when: "Load File"
to WUPage
then: " File loaded successfully"
println " Getting data from Geb Config File: " + lang
}
}
Can you please help me how to do this, as this is very important for me to access and store it in a variable. Thanks
The problem is your #Shared variable. The Geb manual says:
(...) declare a #Shared field. Again it’s best to initialize the field right at the point of declaration. (Semantically, this is equivalent to initializing the field at the very beginning of the setupSpec() method.)
The thing is, setupSpec() runs before GebConfig is evaluated. You can see it if you add this to the end of your GebConfig:
println "Finished evaluating GebConfig"
Then run this Geb specification (I have wrapped the variable assignments into closures and added print statements, then I am evaluating the closures so as to make the assignments work):
package de.scrum_master.stackoverflow
import geb.spock.GebReportingSpec
import spock.lang.Shared
class GebConfigIT extends GebReportingSpec {
#Shared
def sharedLang = {
println "Initialising sharedLang"
System.getProperty("geb.env.lang")
}()
def normalLang = {
println "Initialising normalLang"
System.getProperty("geb.env.lang")
}()
def setup() {
println "sharedLang = $sharedLang"
println "normalLang = $normalLang"
}
def foo() {
expect:
!sharedLang
normalLang
}
def bar() {
expect:
!sharedLang
normalLang
}
}
Initialising sharedLang
Finished evaluating GebConfig
Initialising normalLang
sharedLang = null
normalLang = nl
Initialising normalLang
Finished evaluating GebConfig
sharedLang = null
normalLang = nl
Can you see how sharedLang is initialised only once at the very beginning, before GebConfig even gets a chance to kick in?
Bottom line: Just remove #Shared from your code. It is over-used by most people anyway, they think they save time this way for cheap resources and tamper with their clean fixture setups. #Shared really is only for very expensive resources and very exceptional cases.

Groovy cannot call Classloader (NullpointerException)

I have created the following Groovy Script to transform a JSON Document with a Java Library. But somehow I am not able to load the class from a jar that I need. I always get java.lang.ClassNotFoundException: de.is24.gis.geotools.CoordinateTransformer
The Jar file is in the same directory the groovy script is. I can not edit the way I call the groovy script. It is called automatically by a river.
import groovy.json.JsonSlurper
geo = new GeoTransformer()
geo.transform(ctx.document)
class GeoTransformer {
void transform(doc) {
this.getClass().classLoader.addURL(new File("gis-geotools-1.9.0.jar").toURL())
def CoordinateTransformer = Class.forName("de.is24.gis.geotools.CoordinateTransformer").newInstance();
def x = doc.realEstateCommonData.locationDto.geoCoordinateDto.xCoordinate;
def y = doc.realEstateCommonData.locationDto.geoCoordinateDto.yCoordinate;
def coords = CoordinateTransformer.transformFromLambertEuToWgs84(x,z)
println coords.getLatitude()
println coords.getLongitude()
def jsonObj = new JsonSlurper().parseText( '{"type" : "Point", "coordinates" : [' + coords.getLatitude() + ',' + coords.getLongitude() + ']}' )
doc.location = jsonObj
}
}
Not sure why you can't get to the rooLoader, it must be to do with how this Groovy script is executed.
You could try this (obviously untested)
class GeoTransformer {
void transform( doc ) {
def urlLoader = new GroovyClassLoader()
urlLoader.addURL( new File("gis-geotools-1.9.0.jar").toURL() )
def coordTransformer = Class.forName( "de.is24.gis.geotools.CoordinateTransformer",
true,
urlLoader ).newInstance()
def x = doc.realEstateCommonData.locationDto.geoCoordinateDto.xCoordinate;
def y = doc.realEstateCommonData.locationDto.geoCoordinateDto.yCoordinate;
def coords = coordTransformer.transformFromLambertEuToWgs84( x, z )
println coords.latitude
println coords.longitude
doc.location = [ type:'Point',
coordinates:[ coords.latitude, coords.longitude ] ]
}
}
I got rid of the JsonSlurper bit at the bottom, and just created the map directly (I assume doc.location needs to be a map)?
Edit:
This works in the Groovy Console:
def jar = new File( '/path/to/commons-collections-3.2.1.jar' )
def loader = new GroovyClassLoader()
loader.addURL( jar.toURL() )
def bag = Class.forName( 'org.apache.commons.collections.bag.HashBag',
true,
loader ).newInstance()
bag.add( 'tim' )
println bag
println bag.getClass().name
And prints:
[tim]
org.apache.commons.collections.bag.HashBag
I had the same problem when running groovy from java via GroovyShell.
This solution works for me and solves the dependency-loading problems that MeiSign mentions in tim_yates solution. Explanation follows:
def thisLoader = this.class.classLoader
// try the "proper" way to find the root classloader
def rootLoader = DefaultGroovyMethods.getRootLoader(thisLoader)
if (rootLoader == null) {
// Root classloader is not a groovy RootLoader, but we still need it,
// so walk up the hierarchy and get the top one (whose parent is null)
// When running from Java this is sun.misc.Launcher.ExtClassLoader
rootLoader = thisLoader
ClassLoader parentLoader = rootLoader.getParent()
while (parentLoader != null) {
rootLoader = parentLoader
parentLoader = parentLoader.getParent()
}
}
rootLoader.addURL(new File("gis-geotools-1.9.0.jar").toURL())
def CoordinateTransformer =
Class.forName("de.is24.gis.geotools.CoordinateTransformer",
true,
rootLoader).newInstance();
When running groovy from java using groovy.lang.GroovyShell.main, the this.classLoader is GroovyClassLoader.InnerLoader
When running groovy from command line groovy.bat, the class loader is org.codehaus.groovy.tools.RootLoader
When you call getRootLoader, it walks up the classloader hiearchy - with getParent() - until it finds an instance of a RootLoader. If it doesn't it finds null. (That's the NPE in the title of this question)
The problem is that when running from Java the heirarchy tops out at sun.misc.Launcher.ExtClassLoader which is clearly not a groovy class at all, let alone the groovy RootLoader.
Specifically the hiearchy is:
GroovyClassLoader.InnerLoader
--> GroovyClassLoader
---> sun.misc.Launcher.AppClassLoader
----> sun.misc.Launcher.ExtClassLoader
------> null
How it ends up that way is pretty obscure in GroovyMain (but if you really want to set it yourself there's a GroovyShell constructor that takes a ClassLoader).
Anyway, Tim's solution doesn't work in depth, because the new ClassLoader you are creating on the fly is used only to load that class and not subsequent classes. You really do need to add the classpath entries to the root classpath.
So I simply used the real root when the groovy root fails.
Here's the original code from org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport
/**
* Iterates through the classloader parents until it finds a loader with a class
* named "org.codehaus.groovy.tools.RootLoader". If there is no such class
* <code>null</code> will be returned. The name is used for comparison because
* a direct comparison using == may fail as the class may be loaded through
* different classloaders.
*
* #param self a ClassLoader
* #return the rootLoader for the ClassLoader
* #see org.codehaus.groovy.tools.RootLoader
* #since 1.5.0
*/
public static ClassLoader getRootLoader(ClassLoader self) {
while (true) {
if (self == null) return null;
if (isRootLoaderClassOrSubClass(self)) return self;
self = self.getParent();
}
}
private static boolean isRootLoaderClassOrSubClass(ClassLoader self) {
Class current = self.getClass();
while(!current.getName().equals(Object.class.getName())) {
if(current.getName().equals(RootLoader.class.getName())) return true;
current = current.getSuperclass();
}
return false;
}

Invoke method in dynamically from groovy command line argument

I need to be able to map the first string argument "groovy script.groovy firstArgument" to an method invokation.
script.groovy ==
def firstArgument() {
println "test"
}
"$args"()
Does not work.
Any suggestions?
def firstArgument() {
println "test"
}
def methodName = args[0]
You can use invokeMethod:
invokeMethod(methodName, null)
Or dynamic method invocation, thanks to #tim_yates:
"${methodName}"()

Resources