groovy MissingMethodException - parsing through ConfigSlurper - groovy

I am trying to parse through some properties file using configslurper.
ENT.adminserver.nodenumber=1
ENT.managedserver.1.host=vserver04
ENT.managedserver.2.host=vserver05
ENT.managedserver.3.host=vserver08
ENT.managedserver.4.host=vserver07
Said properties file. I am trying to read the host names from the properties.
Properties properties = new Properties()
File propertiesFile = new File('DomainBuild.properties')
propertiesFile.withInputStream {properties.load(it)}
def config = new ConfigSlurper().parse(properties)
def domainname="ENT" //will be passed through paremeters
def domain = config.get(domainname)
def managedServerFlow= {
println domain.managedserver
println domain.managedserver.keySet()
domain.managedserver.each {
println it.getClass()
println it.get("1")
}
for (server in domain.managedserver) {
println server.getClass()
println server
}
}
}
the it.get("1") is causing the following error.
No signature of method: java.util.LinkedHashMap$Entry.get() is applicable for argument types: (java.lang.String) values: [1]
Possible solutions: getAt(java.lang.String), grep(), grep(java.lang.Object), wait(), getKey(), any()
I looked through the java and groovy doc and spent few hours without resolution. Please help.

Instead of
println it.get("1")
Try
println it.'1'
Or
println it.getAt("1") // as the exception shows you

Think about what types you are working with. config is a ConfigObject, which you can treat like a map. Its sub-objects domain and domain.managedserver are also ConfigObjects. When you call each on domain.managedserver and pass it a closure that takes no parameters, it gives you a set of Entries. Therefore you can't call it.get("1") because an Entry doesn't have a property called "1". It has key and value. So you can either println "$it.key: $it.value" or
domain.managedserver.each { key, value ->
println value.getClass()
println "$key: $value"
}
or if you want to get the value for key "1" directly:
println domain.managedserver.'1'

Related

groovy evaluate string as the function which exists in the same script

I am trying to evaluate string as code in groovy and it is failing with groovy.lang.MissingMethodException exception even though method exists in the same script. As I understood groovy runs new instance every time it tries to evaluate the code, but is there any way to inject current script into Eval.me or GroovyShell().evaluate() so that it can find the method and runs it ?
Below is sample code snippet,
def justSayHello(){
return "hello"
}
def my_str = "justSayHello()"
//Eval.me(my_func_str)
new GroovyShell().evaluate(my_func_str)
Both Eval and GroovyShell().evaluate() are throwing below exception
Caught: groovy.lang.MissingMethodException: No signature of method: Script1.justSayHello() is applicable for argument types: () values: []
groovy.lang.MissingMethodException: No signature of method: Script1.justSayHello() is applicable for argument types: () values: []
at Script1.run(Script1.groovy:1)
at string_split.run(string_split.groovy:35)
The following code:
justSayHello = {
println "hello"
}
def my_str = "justSayHello()"
new GroovyShell(binding).evaluate(my_str)
prints out hello when run. Here we have changed justSayHello from a method (on an implicit class that you can not see but which the groovy compiler generates around your script) to a closure. Further we are not doing def justSayHello as that would be defining it as a field on the implicit surrounding class (again which you can't see, but it's there), but rather just defining the variable without any modifiers which puts it in the global binding of the script.
We then send in the binding to the GroovyShell so that it can find the variable.
Result:
─➤ groovy solution.groovy
hello
A more generic variant is to do something like this:
def justSayHello() {
println "hello"
}
def someOtherMethod() {
println "hello again"
}
def methods = this.class.declaredMethods.findResults { m ->
if (m.name.startsWith('$') || m.name in ['main', 'run']) return null
[m.name, this.&"${m.name}"]
}.collectEntries { it }
// just for debugging, print the methods
methods.each { k, v ->
println "method: $k"
}
def my_str = "justSayHello()"
new GroovyShell(new Binding(methods)).evaluate(my_str)
which prints:
─➤ groovy solution.groovy
method: justSayHello
method: someOtherMethod
hello
here we find all the declared methods in the implicit class generated by groovy, remove some stuff added by the groovy compiler (namely main, run and methods starting with a $) and then send the resulting map as the binding for the GroovyShell constructor.
I suspect there might be a more elegant way of accomplishing this, so any groovy gurus - feel free to correct me here.
For an explanation of the implicit enclosing class for a groovy script, see for example this stackoverflow answer.

How can I use Arrow-kt's Some() in Groovy code?

I'm trying to test my Kotlin code, which has Arrow-kt types, using Spock in Groovy. However, I'm not able to use Arrow-kt's additions such as Some. For example, I have a test as follows:
#Unroll
def "add returns #expected for queryRecord #queryRecord"() {
given:
def ip = "ip"
def rule = "rule"
when:
def result = unit.add(ip, rule)
then:
1 * dynamoDBMapperMock.load(ActionRecord.class, ip) >> queryRecord
result == expected
where:
queryRecord | expected
new ActionRecord() | None.INSTANCE
null | Some(new ActionInternal("ip"))
}
While the first data row succeeds with no problems, the second one fails with the following error:
groovy.lang.MissingMethodException: No signature of method: package.name.EventSpec.Some() is applicable for argument types: (package.name.ActionInternal) values: [ActionInternal(dropletIp=null)]
Possible solutions: Mock(), Spy(), Stub(), dump(), Mock(groovy.lang.Closure), Mock(java.lang.Class)
I've tried .some() as well, but not to avail. Apparently Groovy can't access Kotlin extensions, but Some is simply a data class[1], so I'm not sure why I cannot use it in Groovy.
Yes, you can use Arrow Datatypes in Groovy, the result is not as idiomatic as in Kotlin because the library heavily depends on extension functions and functions in the companion object
Example
import arrow.core.Option
import static arrow.core.OptionKt.getOrElse
static main(args){
println 'What is your name?'
def name = Option.#Companion.fromNullable(System.in.newReader().readLine())
.filterNot { it.isEmpty() }
.map { it.toUpperCase() }
println("Welcome ${getOrElse(name) { 'Anonymous' }}")
}
Output
'Welcome JOHN' (or 'Welcome Anonymous' if the provided name is null or empty)
As you can see, to be able to use getOrElse extension function, we need to import it as a static method
Hint
Do not use Some directly unless you are absolutely sure the value is not null, otherwise, you should rely on Option.fromNullable to safely lift the value to the Option context (i.e create Some or None depending if the value is null or not)

Groovy MissingMethodException with signature when accessing getter and NoSuchMethodError on inheritance

Can you explain why sometimes groovy throws MissingMethodException when java code calls a getter and there's a property with the same name?
Secondary question:
Can you explain why my first work-around is invalid for the 2nd use case?
The following script works because I added methodMissing
#!/usr/bin/env groovy
#Grapes([
#Grab(group='org.jvnet.hudson', module='xstream', version='1.4.7-jenkins-1'),
])
def a
println 'xstream'
com.thoughtworks.xstream.XStream s = new com.thoughtworks.xstream.XStream()
println s
def reg1 = s.converterRegistry
println "using property: $reg1"
com.thoughtworks.xstream.XStream.metaClass.methodMissing = { String name, def args ->
println "missing $name"
if (name=="getConverterRegistry") {
return delegate.converterRegistry
}
}
def reg2 = s.getConverterRegistry()
println "using getter : $reg2"
println "ok"
This script prints:
com.thoughtworks.xstream.XStream#6c45ee6e
using property: com.thoughtworks.xstream.XStream$2#2e8e8225
missing getConverterRegistry
using getter : com.thoughtworks.xstream.XStream$2#2e8e8225
ok
The method getConverterRegistry exists in XStream but if you comment out the methodMissing you get :
groovy.lang.MissingMethodException: No signature of method: com.thoughtworks.xstream.XStream.getConverterRegistry() is applicable for argument types: () values: []
at TestXStream.run(TestXStream.groovy:24)
I was full of hope when making this work (although it's not pretty) but my issue continues because my code is actually using jenkins library and the following code does not work:
#!/usr/bin/env groovy
import hudson.util.XStream2
import com.thoughtworks.xstream.XStream
#Grapes([
#Grab(group='org.jvnet.hudson', module='xstream', version='1.4.7-jenkins-1'),
#Grab(group='org.jenkins-ci.main', module='jenkins-core', version='1.642.3', transitive=false),
])
def a
XStream.metaClass.methodMissing = { String name, def args ->
println "missing $name for XStream"
if (name=="getConverterRegistry") {
return delegate.converterRegistry
}
}
def reg2 = new XStream().getConverterRegistry()
println reg2
XStream2.metaClass.methodMissing = { String name, def args ->
println "missing $name for XStream2"
if (name=="getConverterRegistry") {
return delegate.converterRegistry
}
}
println 'xstream2'
XStream2 s2 = new XStream2() // internal call to this.getConverterRegistry()
println "ok"
And the output:
missing getConverterRegistry for XStream
com.thoughtworks.xstream.XStream$2#c2db68f
xstream2
Caught: java.lang.NoSuchMethodError: hudson.util.XStream2.getConverterRegistry()Lcom/thoughtworks/xstream/converters/ConverterRegistry;
java.lang.NoSuchMethodError: hudson.util.XStream2.getConverterRegistry()Lcom/thoughtworks/xstream/converters/ConverterRegistry;
at hudson.util.XStream2.wrapMapper(XStream2.java:188)
at com.thoughtworks.xstream.XStream.buildMapper(XStream.java:610)
at com.thoughtworks.xstream.XStream.<init>(XStream.java:568)
at com.thoughtworks.xstream.XStream.<init>(XStream.java:496)
at com.thoughtworks.xstream.XStream.<init>(XStream.java:465)
at com.thoughtworks.xstream.XStream.<init>(XStream.java:411)
at com.thoughtworks.xstream.XStream.<init>(XStream.java:350)
at hudson.util.XStream2.<init>(XStream2.java:89)
at TestXStream2.run(TestXStream2.groovy:33)
Class XStream contains a property converterRegistry and its getter.
XStream2 extends XStream and the getter is inherited.
Note that when I run this from eclipse it's working fine and when using CLI I have this issue; possibly because eclipse would change this code more than the compiler.
Any clues?
I dropped the issue by switching back to plain Java for the main launcher.
I use the same über jar as a dependency as when I used groovy and grape.
I don't know wether it's related to groovy or grape (I suspect groovy) but I worked around it.

Groovy collect returning GString in a Jenkins Workflow script

It seems that in the following piece of code:
def formattedPaths = affectedFiles.collect {
"${it.editType.name} ${it.path}"
}
at least sometimes formattedPaths evaluates to a GString instead of a List. This piece of code is a fragment of a larger Jenkins Workflow script, something like:
node {
currentBuild.rawBuild.changeSets[0].collect {
"""<b>${it.user}</b> # rev. ${it.revision}: ${it.msg}
${affectedFilesLog(it.affectedFiles)}"""
}
}
def affectedFilesLog(affectedFiles) {
println "Affected files [${affectedFiles.class}]: $affectedFiles"
def formattedPaths = affectedFiles.collect {
"${it.editType.name} ${it.path}"
}
println "formattedPaths [${formattedPaths.class}]: $formattedPaths"
formatItemList(formattedPaths)
}
def formatItemList(list) {
if (list) {
return list.join('\n')
}
return '(none)'
}
Running this script in Jenkins produces output:
Running: Print Message
Affected files [class java.util.ArrayList]: [hudson.scm.SubversionChangeLogSet$Path#5030a7d8]
Running: Print Message
formattedPaths [class org.codehaus.groovy.runtime.GStringImpl]: edit my/path/flow.groovy
(...)
groovy.lang.MissingMethodException: No signature of method: java.lang.String.join() is applicable for argument types: (java.lang.String) values: [
]
And this makes me believe that in the code:
println "Affected files [${affectedFiles.class}]: $affectedFiles"
def formattedPaths = affectedFiles.collect {
"${it.editType.name} ${it.path}"
}
println "formattedPaths [${formattedPaths.class}]: $formattedPaths"
affectedFiles is ArrayList (script output Affected files [class java.util.ArrayList]: [hudson.scm.SubversionChangeLogSet$Path#5030a7d8] in the output)
but result of running collect method on it - assigned to formattedPaths - is a GString (output: formattedPaths [class org.codehaus.groovy.runtime.GStringImpl]: edit my/path/flow.groovy)
Shouldn't the collect method always return a List?
After the discussion in the comments pointing that it may be some side effects done by the Jenkins Workflow plugin, I decided to use a plain for-each loop:
def affectedFilesLog(affectedFiles) {
println "Affected files [${affectedFiles.class}]: $affectedFiles"
def ret = ""
for (Object affectedFile : affectedFiles) {
ret += affectedFile.path + '\n'
}
println("affectedFilesLog ret [${ret.class}]: $ret")
if (!ret) {
return '(brak)'
}
return ret;
}
EDIT 19/11/2015:
Jenkins workflow plugin mishandles functions taking Closures, see https://issues.jenkins-ci.org/browse/JENKINS-26481 and its duplicates. So rewriting the code to a plain Java for-each loop was the best solution.
You cannot currently use the collect method. JENKINS-26481
I guess your code is not thread safe. If you pass some objects as paramters to some other functions, do not change this. Always create and return a new changed object. Do not manipulate the original data. You should check where your objects live. Is it just inside a function or in global scope?

External Content with Groovy BuilderSupport

I've built a custom builder in Groovy by extending BuilderSupport. It works well when configured like nearly every builder code sample out there:
def builder = new MyBuilder()
builder.foo {
"Some Entry" (property1:value1, property2: value2)
}
This, of course, works perfectly. The problem is that I don't want the information I'm building to be in the code. I want to have this information in a file somewhere that is read in and built into objects by the builder. I cannot figure out how to do this.
I can't even make this work by moving the simple entry around in the code.
This works:
def textClosure = { "Some Entry" (property1:value1, property2: value2) }
builder.foo(textClosure)
because textClosure is a closure.
If I do this:
def text = '"Some Entry" (property1:value1, property2: value2)'
def textClosure = { text }
builder.foo(textClosure)
the builder only gets called for the "foo" node. I've tried many variants of this, including passing the text block directly into the builder without wrapping it in a closure. They all yield the same result.
Is there some way I take a piece of arbitrary text and pass it into my builder so that it will be able to correctly parse and build it?
Your problem is that a String is not Groovy code. The way ConfigSlurper handles this is to compile the text into an instance of Script using GroovyClassLoader#parseClass. e.g.,
// create a Binding subclass that delegates to the builder
class MyBinding extends Binding {
def builder
Object getVariable(String name) {
return { Object... args -> builder.invokeMethod(name,args) }
}
}
// parse the script and run it against the builder
new File("foo.groovy").withInputStream { input ->
Script s = new GroovyClassLoader().parseClass(input).newInstance()
s.binding = new MyBinding(builder:builder)
s.run()
}
The subclass of Binding simply returns a closure for all variables that delegates the call to the builder. So assuming foo.groovy contains:
foo {
"Some Entry" (property1:value1, property2: value2)
}
It would be equivalent to your code above.
I think the problem you described is better solved with a slurper or parser.
See:
http://groovy.codehaus.org/Reading+XML+using+Groovy%27s+XmlSlurper
http://groovy.codehaus.org/Reading+XML+using+Groovy%27s+XmlParser
for XML based examples.
In your case. Given the XML file:
<foo>
<entry name='Some Entry' property1="value1" property2="value2"/>
</foo>
You could slurp it with:
def text = new File("test.xml").text
def foo = new XmlSlurper().parseText(text)
def allEntries = foo.entry
allEntries.each {
println it.#name
println it.#property1
println it.#property2
}
Originally, I wanted to be able to specify
"Some Entry" (property1:value1, property2: value2)
in an external file. I'm specifically trying to avoid XML and XML-like syntax to make these files easier for regular users to create and modify. My current solution uses ConfigSlurper and the file now looks like:
"Some Entry"
{
property1 = value1
property2 = value2
}
ConfigSlurper gives me a map like this:
["Some Entry":[property1:value1,property2:value2]]
It's pretty simple to use these values to create my objects, especially since I can just pass the property/value map into the constructor.

Resources