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.
Related
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.
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'
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?
I have developed a number of groovys used as plugins by Serviio.
Many of the methods used by these plugins are common, but when changes are made, each plugin needs to be updated. Therefore I want to "include" those methods in each plugin from a tools.groovy. I have tried 2 different approaches suggested in other posts.
I tried using
evaluate(new File("C:\\Program Files\\Serviio\\plugins\\tools.groovy"))
at the start of each plugin where tools.groovy just has
class Tools{method1{return}method2{return}}
but when executing the plugin I get
Caught: groovy.lang.MissingMethodException: No signature of method: Tools.main() is applicable for argument types: () values: []
If I then add
void main(args) { }
to class Tools, the error goes away but that Tools.main is run instead of the plugin.main and I get no output.
My second approach as suggested was to use
def script = new GroovyScriptEngine( '.' ).with {
loadScriptByName( 'C:\\Program Files\\Serviio\\plugins\\tools.groovy' )
}
this.metaClass.mixin script
This however gives the error
unexpected token: this # line 55, column 2.
this.metaClass.mixin script
Any suggestions on how to make either of these solutions work would be appreciated.
Did you try defining a common base script and giving it as a compiler configuration.
http://groovy.codehaus.org/Embedding+Groovy
From the groovy documentation...
class ScriptBaseTest {
#Test
void extend_groovy_script() {
def compiler = new CompilerConfiguration()
compiler.setScriptBaseClass("ScriptBaseTestScript")
def shell = new GroovyShell(this.class.classLoader, new Binding(), compiler)
assertEquals shell.evaluate("foo()"), "this is foo"
}
}
abstract class ScriptBaseTestScript extends Script {
def foo() {
"this is foo"
}
}
A SoapUI project can run random script upon load.
Load Script is invoked with log and project variables.
In my shared lib I have method - addAsserts() that traverses the whole project and adds schema compliance assertions to SOAP test steps. In my Load Script I call shared method
addAsserts(this)
passing 'this' as a parameter and set closure.delegate to it inside addAsserts method to make 'project' variable accessible within the closure scope
addAsserts method is defined in sharedUtil.groovy:
static def addAsserts(that){
def closure={
project.testSuites.each { testSuiteName, testSuiteObject ->
testSuiteObject.testCases.each { testCaseName, testCaseObject ->
testCaseObject.testSteps.each { testStepName, testStepObject ->
if ("class com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep" == testStepObject.getClass().toString() ) {
log.info "adding 'Schema Compliance' assertion to ${testSuiteName}/${testCaseName}/${testStepName}"
testStepObject.addAssertion('Schema Compliance')
}
}
}
}
}//closure
closure.delegate=that // <--- i would like NOT to pass 'that' as parameter
// but rather detect in runtime with some kind of
// getCallerInstance() method
return closure.call()
}
QUESTION:
Is it possible to detect caller instance in runtime with some kind of getCallerInstance() method ?
No, I don't believe this is possible. Wasn't in Java either (you can find out the name/method of the calling class using some horrible stacktrace hacking, but not the instance of the class itself)
Edit...
It might be possible with a Category (but I am not experienced with SoapUI, so I don't know if this technique would fit)
Say we have a class Example defined like so:
class Example {
String name
}
We can then write a class very similar to your example code, which in this case will set the delegate of the closure, and the closure will print out the name property of the delegate (as we have set the resolve strategy to DELEGATE_ONLY)
class AssetAddingCategory {
static def addAsserts( that ) {
def closure = {
"Name of object: $name"
}
closure.delegate = that
closure.resolveStrategy = Closure.DELEGATE_ONLY
closure.call()
}
}
Later on in our code, it is then possible to do:
def tim = new Example( name:'tim' )
use( AssetAddingCategory ) {
println tim.addAsserts()
}
And this will print out
Name of object: tim