I'm trying to create a field mapping to map fields from user-friendly names to member variables in a variety of domain objects. The larger context is that I'm building up an ElasticSearch query based on user-constructed rules stored in a database, but for the sake of MCVE:
class MyClass {
Integer amount = 123
}
target = new MyClass()
println "${target.amount}"
fieldMapping = [
'TUITION' : 'target.amount'
]
fieldName = 'TUITION'
valueSource = '${' + "${fieldMapping[fieldName]}" + '}'
println valueSource
value = Eval.me('valueSource')
The Eval fails. Here's the output:
123
${target.amount}
Caught: groovy.lang.MissingPropertyException: No such property: valueSource for class: Script1
groovy.lang.MissingPropertyException: No such property: valueSource for class: Script1
at Script1.run(Script1.groovy:1)
at t.run(t.groovy:17)
What's necessary to evaluate the generated variable name and return the value 123? It seems like the real problem is that it's not recognizing that valueSource has been defined, not the actual expression held in valueSource, but that could be wring, too.
You're almost there, but you need to use a slightly different mechanism: the GroovyShell. You can instantiate a GroovyShell and use it to evaluate a String as a script, returning the result. Here's your example, modified to work properly:
class MyClass {
Integer amount = 123
}
target = new MyClass()
fieldMapping = [
'TUITION' : 'target.amount'
]
fieldName = 'TUITION'
// These are the values made available to the script through the Binding
args = [target: target]
// Create the shell with the binding as a parameter
shell = new GroovyShell(args as Binding)
// Evaluate the "script", which in this case is just the string "target.amount".
// Inside the shell, "target" is available because you added it to the shell's binding.
result = shell.evaluate(fieldMapping[fieldName])
assert result == 123
assert result instanceof Integer
Related
I have groovy code as below:
def randomInt = RandomUtil.getRandomInt(1,200);
log.info randomInt
def chars = (("1".."9") + ("A".."Z") + ("a".."z")).join()
def randomString = RandomUtil.getRandomString(chars, randomInt) //works well with this code
log.info randomString
evaluate("log.info new Date()")
evaluate('RandomUtil.getRandomString(chars, randomInt)') //got error with this code
I want to evaluate a String which one like a {classname}.{methodname} in SoapUI with Groovy, just like above, but got error here, how to handle this and make it works well as I expect?
I have tried as blew:
evaluate('RandomUtil.getRandomString(chars, randomInt)') //got error with this code
Error As below:
Thu May 23 22:26:30 CST 2019:ERROR:An error occurred [No such property: getRandomString(chars, randomInt) for class: com.hypers.test.apitest.util.RandomUtil], see error log for details
The following code:
log = [info: { println(it) }]
class RandomUtil {
static def random = new Random()
static int getRandomInt(int from, int to) {
from + random.nextInt(to - from)
}
static String getRandomString(alphabet, len) {
def s = alphabet.size()
(1..len).collect { alphabet[random.nextInt(s)] }.join()
}
}
randomInt = RandomUtil.getRandomInt(1, 200)
log.info randomInt
chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
def randomString = RandomUtil.getRandomString(chars, 10) //works well with this code
log.info randomString
evaluate("log.info new Date()")
evaluate('RandomUtil.getRandomString(chars, randomInt)') //got error with this code
emulates your code, works, and produces the following output when run:
~> groovy solution.groovy
70
DDSQi27PYG
Thu May 23 20:51:58 CEST 2019
~>
I made up the RandomUtil class as you did not include the code for it.
I think the reason you are seeing the error you are seeing is that you define your variables char and randomInt using:
def chars = ...
and
def randomInt = ...
this puts the variables in local script scope. Please see this stackoverflow answer for an explanation with links to documentation of different ways of putting things in the script global scope and an explanation of how this works.
Essentially your groovy script code is implicitly an instance of the groovy Script class which in turn has an implicit Binding instance associated with it. When you write def x = ..., your variable is locally scoped, when you write x = ... or binding.x = ... the variable is defined in the script binding.
The evaluate method uses the same binding as the implicit script object. So the reason my example above works is that I omitted the def and just typed chars = and randomInt = which puts the variables in the script binding thus making them available for the code in the evaluate expression.
Though I have to say that even with all that, the phrasing No such property: getRandomString(chars, randomInt) seems really strange to me...I would have expected No such method or No such property: chars etc.
Sharing the code for for RandomUtil might help here.
As per ref-doc:
A GroovyClassLoader keeps a reference of all the classes it created, so it is easy to create a memory leak. In particular, if you execute the same script twice, if it is a String, then you obtain two distinct classes!
I use a file as a source for parsing but turned caching off:
GroovyCodeSource src = new GroovyCodeSource( file )
src.cachable = false
Class clazz = groovyClassLoader.parseClass src
Class clazz1 = groovyClassLoader.parseClass src
log.info "$clazz <=> $clazz1 equal: ${clazz == clazz1}"
the log output is always
class MyClass <=> class MyClass equal: false
If I comment the line src.cachable = false, then the class instances become equal, but they are NOT re-compiling even though the underlying file has changed.
Hence the question: how can I re-compile classes properly without creating a memory leak?
i did the following test and don't see any memory leak.
as for me, the normal GC work.
the link to your class will be alive while any instance of class is alive.
GroovyCodeSource src = new GroovyCodeSource( 'println "hello world"', 'Test', '/' )
src.cachable = false
def cl=this.getClass().getClassLoader()
for(int i=0;i<1000000;i++){
Class clazz = cl.parseClass src
if(i%10000==0)println "$i :: $clazz :: ${System.identityHashCode(clazz)}"
}
After doing some experiments, I figured out that switching back to using String:
String src = 'class A {}'
Class clazz = groovyClassLoader.parseClass src
log.info groovyClassLoader.loadedClasses.join( ', ' )
the loaded classes do not change in lenght, even if a class has some Closures inside (which appear as classes as well).
I am hoping to get an explanation as to why the call to this Groovy method works as expected:
def f1(int n) {
return n + 1
}
println f1(1) // -> 2
But, if the parameter is not specifically defined ("def n" instead of "int n"), the method call needs to change:
def f2(def n) {
return n + 1
}
println f2(1) // Exception: Illegal class name
println this.&f2(1) // -> 2
What is happening under the hood to make this necessary?
UPDATED with more info:
This is on Windows with Groovy 2.4.5 JVM 1.8.0_51
The entire script is those 9 lines in a file called 1.groovy - nothing else.
I am running this from the console (cmdr) using "groovy 1.groovy"
The error on line 8 is:
Caught: java.lang.ClassFormatError: Illegal class name "3$f2" in class file 3$f2
java.lang.ClassFormatError: Illegal class name "3$f2" in class file 3$f2
at 3.run(3.groovy:8)
This is related to the name of your Script. When you have a file "1.groovy", Groovy generate a class with the name "1" in the default package, which is not a valid class name.
When you use f2(n) without a type, as this method is "too generic", Groovy try to find a matching method, or a class named f2, or an inner class named f2 : loading an inner class f2 of the class 1 fail, because the name is invalid.
How to get values from properties file using Groovy?
I require to have a property file (.properties) which would have file names as key, and their destination path as the value. I will need the key to be resolved at runtime, depending on file that needs to be moved.
So far I am able to load the properties it seems but can't "get" the value from the loaded properties.
I referred to the thread : groovy: How to access to properties file? and following is the code snippet i have so far
def props = new Properties();
File propFile =
new File('D:/XX/XX_Batch/XX_BATCH_COMMON/src/main/resources/patchFiles.properties')
props.load(propFile.newDataInputStream())
def config = new ConfigSlurper().parse(props)
def ant = new AntBuilder()
def list = ant.fileScanner {
fileset(dir:getSrcPath()) {
include(name:"**/*")
}
}
for (f in list) {
def key = f.name
println(props)
println(config[key])
println(config)
def destn = new File(config['a'])
}
the properties file has the following entries for now :
jan-feb-mar.jsp=/XX/Test/1
XX-1.0.0-SNAPSHOT.jar=/XX/Test/1
a=b
c=d
Correct values are returned if I look up using either props.getProperty('a')
or,
config['a']
Also tried the code: notation
But as soon as switch to using the variable "key", as in config[key] it returns --> [:]
I am new to groovy, can't say what am i missing here.
It looks to me you complicate things too much.
Here's a simple example that should do the job:
For given test.properties file:
a=1
b=2
This code runs fine:
Properties properties = new Properties()
File propertiesFile = new File('test.properties')
propertiesFile.withInputStream {
properties.load(it)
}
def runtimeString = 'a'
assert properties."$runtimeString" == '1'
assert properties.b == '2'
Unless File is necessary, and if the file to be loaded is in src/main/resources or src/test/resources folder or in classpath, getResource() is another way to solve it.
eg.
def properties = new Properties()
//both leading / and no / is fine
this.getClass().getResource( '/application.properties' ).withInputStream {
properties.load(it)
}
//then: "access the properties"
properties."my.key"
Had a similar problem, we solved it with:
def content = readFile 'gradle.properties'
Properties properties = new Properties()
InputStream is = new ByteArrayInputStream(content.getBytes());
properties.load(is)
def runtimeString = 'SERVICE_VERSION_MINOR'
echo properties."$runtimeString"
SERVICE_VERSION_MINOR = properties."$runtimeString"
echo SERVICE_VERSION_MINOR
Just in case...
If a property key contains dot (.) then remember to put the key in quotes.
properties file:
a.x = 1
groovy:
Properties properties ...
println properties."a.x"
Properties properties = new Properties()
properties.load(new File("path/to/file.properties").newReader())
Just another way of doing it. Use this if it works for you. :)
Properties properties = new Properties()
//loading property file
File propertiesFile = new File(this.class.getResource('application.properties').getPath())
propertiesFile.withInputStream {
properties.load(it)
}
//Accessing the value from property file
properties.getProperty('userName')
With static method extension:
Properties.metaClass.static.fromFile =
{file -> new Properties().with{new File(file).withInputStream it.&load;it}}
def properties = Properties.fromFile('test.properties')
Groovy for getting value of property from "local.properties" by giving key.
Example- For finding value of this property's key is "mail.smtp.server"
In V5
ctx.getBean("configurationService")
configurationService = ctx.getBean("configurationService")
String value = configurationService.getConfiguration().getString("mail.smtp.server","")
In 1905
spring.getBean("configurationService")
configurationService = spring.getBean("configurationService")
String value = configurationService.getConfiguration().getString("mail.smtp.server","")
I have one groovy script which print some statistics: println: "..."
now I have another groovy script which needs this data. Is possible somehow run first script from second and save this data as paramater and then work with them from second script ? I just know how to run script: with GroovyShell() and then run(...) but this doesnt return output of first script
A few options:
If you're calling it from a script, redefine stdout.
Fix the first script so it prints data retrieved from a class, and re-write the calling script to use that class instead of relying on the printed output from the first. Long-term may be the best option.
Use a pipe on the command line: groovy s1.groovy | groovy s2.groovy
Personally, when composing things that do stuff with stdin/stdio, I prefer the last method. Example:
s1.groovy
5.times { println it }
s2.groovy
r = new BufferedReader(new InputStreamReader(System.in))
while (l = r.readLine()) { println((l as Integer) * 2) }
Output
$ groovy s1.groovy
0
1
2
3
4
$ groovy s1.groovy | groovy s2.groovy
0
2
4
6
8
One way to do this would be to set the out parameter in the binding when calling the first script:
So given a script s1.groovy:
//Print the letters of 'tim_yates', one per line
'tim_yates'.each this.&println
We can do (in s2.groovy)
// Create a StringWriter that will capture output
String output = new StringWriter().with { sw ->
// And make a Binding for our script
new Binding().with { b ->
// Set 'out' in the Binding to be our StringWriter
b[ 'out' ] = sw
// evaluate the file with the GroovyShell (using the binding)
new GroovyShell( b ).evaluate( new File( 's1.groovy' ) )
}
// And return the String captured in our writer
sw.toString()
}
println output
And then run it with groovy s2.groovy
Edit
I think this is option #1 in Dave's answer...