GStringImpl in groovy File() constructor - string

I am running into a bit of a strange error here when creating a new file object with a GStringImpl. If I create a new File (and then list files in that path) with a GStringImpl, I get an empty array, and no error, however if I just a normal string, I get a list of files... While that makes sense in a way I would think that there would be an error somewhere.
Example:
def thisIsAListOfFiles = new File("/absolute/fs/mount/point").listFiles()
def gString = "${StaticClass.propertyStringThatIsAnAbsoluteFilePath}"
def notAListOfFiles = new File(gString).listFiles()
Any thoughts on what is going on here? Is this the expected behavior?
More info:
Groovy Version: 2.1.3
Grails version: 2.2.2 (this is within a grails app of course)
Java version: OpenJDK Runtime Environment (IcedTea 2.3.9)
I start out with a properties file with a bunch of properties like this
com.mycompany.property = "/absolute/directory/path"
Because I cant easily inject grailsApplication into non grails classes (anything in /src/groovy for instance) I inject grailsApplication into bootstrap, and use groovy config slurper to read the properties file from the classpath and set then as static string values in a groovy class Config.groovy. That groovy class then has the static variables of all of the properties I need anywhere within the application.
Note: this is not an issue reading the properties files, or anything along those lines. I log the Config.filePathProperty right before the new File(var).listFiles() occurs and that value is set properly.

I'm pretty sure your static path is set incorrectly. I ran the following code as a test:
String path = '/etc/'
print "String ($path): "
println(new File(path).listFiles().size())
def gpath = "${path}"
print "GString ($gpath): "
println(new File(gpath).listFiles().size())
class Foo {
static String path = '/etc/'
}
print "GString static ($Foo.path): "
println(new File("${Foo.path}").listFiles().size())
And got this result (obviously your file count will vary):
String (/etc/): 122
GString (/etc/): 122
GString static (/etc/): 122
The only time I saw a null result was when the path was invalid, for example:
assert new File("does-not-exist").listFiles() == null
One thing you could do would be to eliminate the GString, which is unnecessary in your example:
def notAListOfFiles = new File(StaticClass.propertyStringThatIsAnAbsoluteFilePath).listFiles()
But I'm sure you'll find a typo in the variable or file path, or another similar issue.

Related

how to share data between groovy files?

I have one common groovy file that contains few const variables and functions...
and I also have more groovy files with pipelineJob that use the variables and functions from the common file
what is the best way to import all the data from the common file to the other files?
I have not tested this with Jenkins, but if Jenkins executes the Groovy script as if by invoking groovy -cp .... myScript.groovy it should work:
utils.groovy:
// notice there's no "def", otherwise the def would be local only
name = 'Joe'
class MyUtils {
static String greeting(String name) {
"Hello $name"
}
}
src/main.groovy
def shell = new GroovyShell(getBinding())
shell.evaluate(new File('utils.groovy'))
println MyUtils.greeting(name)
Running it:
$ groovy src/Main.groovy
Hello Joe
Because the Script base class by default also has an evaluate method, your can actually just call that instead of using a GroovyShell and the result should be identical:
src/main.groovy
evaluate(new File('utils.groovy'))
println MyUtils.greeting(name)
If it doesn't work it's because the Script base class has been changed , probably... the first approach should work in all cases.

Camunda: Use in an external Groovy script a provided Class

I want to use for scripting external Groovy Scripts.
To not copy a lot of code, I want to share classes.
I have:
- external_test.groovy
- Input.groovy
Running the external_test.groovy in Intellij works.
Input is a simple class:
package helpers
class Input {
String serviceConfig
String httpMethod
String path
LinkedHashMap headers = [:]
String payload
Boolean hasResponseJson
}
When the script is executed by Camunda, it cannot find the class:
import helpers.Input
...
And throws an Exception:
unable to resolve class helpers.Input # line 16, column 9. new helpers.Input(serviceConfig: "camundaService", ^ 1 error
It is listed in the Deployment:
Do I miss something or is this not supported?
I found a post in the Camunda forum, that helped me to solve this:
https://forum.camunda.org/t/groovy-files-cant-invoke-methods-in-other-groovy-files-which-are-part-of-same-deployment/7750/5
Here is the solution (that is not really satisfying - as it needs a lot of boilerplate code):
static def getScript(fileName, execution) {
def processDefinitionId = execution.getProcessDefinitionId()
def deploymentId = execution.getProcessEngineServices().getRepositoryService().getProcessDefinition(processDefinitionId).getDeploymentId()
def resource = execution.getProcessEngineServices().getRepositoryService().getResourceAsStream(deploymentId, fileName)
def scannerResource = new Scanner(resource, 'UTF-8')
def resourceAsString = scannerResource.useDelimiter('\\Z').next()
scannerResource.close()
GroovyShell shell = new GroovyShell()
return shell.parse(resourceAsString)
}
def helper = getScript("helpers/helper_classes.groovy", execution)
helper.myFunction("hello")

Using a String as code with Groovy XML Parser

I am new to groovy - I am hoping this is a simple thing to solve. I am reading in an xml document, and then I am able to access data like this:
def root = new XmlParser().parseText(xmlString)
println root.foo.bar.text()
What I would like to do, is to have loaded the "foo.bar" portion of the path from a file or data base, so that I can do something like this:
def paths = ["foo.bar","tashiStation.powerConverter"] // defined for this example
paths.each {
path ->
println path + "\t" + root.path.text()
}
Obviously the code as written does not work... I thought maybe this would work:
paths.each {
path ->
println path + "\t" + root."${path}".text()
}
...but it doesn't. I based my initial solution on pg 153 of Groovy for DSL where dynamic methods can be created in a similar way.
Thoughts? The ideal solution will not add significant amounts of code and will not add any additional library dependencies. I can always fall back to doing this stuff in Java with JDOM but I was hoping for an elegant groovy solution.
This is very similar to this question from 3 days ago and this question
You basically need to split your path on . and then walk down this list moving through your object graph
def val = path.split( /\./ ).inject( root ) { obj, node -> obj?."$node" }?.text()
println "$path\t$val"
Should do it in this instance :-)

What function is meant to format/substitute {0} {1} parameters in an string in Grails/Groovy?

I'm just getting started with Groovy/Grails
I noticed the error messages you get when you validate a form look like this:
Property [{0}] of class [{1}] cannot be blank
For example this code to dump the errors to the console
s.errors.allErrors.each
{
println it.defaultMessage
}
Now, it.arguments contains the arguments that need to be filled in here.
The problem is, I can't find any method in the Grails or Groovy documentation that formats strings based on positional parameters like {0}, {1} and substitutes values from an array
I need something like python's %
What is the proper way to format these error strings so the parameters get substituted properly?
These markers are actually replaced using the standard java.text.MessageFormat APIs. If you display the messages using Grail's g:message tag, it will fill in the gaps if you pass a suitable args="..." attribute:
<g:message code="mymessagecode" args="${['size', 'org.example.Something']}"/>
Under certain circumstances (within GSP pages and from controllers IIRC) you cann call the tag like a function:
g.message(code:'mymessagecode',args: ['size', 'org.example.Something'])
Note, that the value to supply as message code is only a symbolic string constant. The actual translation (the message text with the "gaps" in it) will be read by the framework using Spring's reloadable resource bundles.
If all you actually have is a translation text, you can call the message formatting APIs directly. See for example:
import java.text.MessageFormat
...
args = ["english"].toArray()
println(MessageFormat.format("Translation into {0}", args))
// Or - as the method is variadic:
println(MessageFormat.format("Translation into {0}", "english"))
Look what Groovy can do for you, using a little bit of meta-programming.
MessagesBundle_en_US.properties:
greetings = Hello {0}.
inquiry = {0}: How are you {1}?
farewell = Goodbye.
ResourceBundleWithSugar.groovy:
import java.text.MessageFormat
class ResourceBundleUtils {
def propertyMissing(String name) { this.getString(name) }
def methodMissing(String name, args) {
MessageFormat.format(this.getString(name), args)
}
}
ResourceBundle.metaClass.mixin ResourceBundleUtils
def msg = ResourceBundle.getBundle("MessagesBundle", new Locale("en","US"));
println msg.greetings("Serge")
println msg.inquiry("Serge","Mary")
println msg.farewell // You can use also: msg.['farewell'] msg."farewell" or msg.getString("farewell")
Output:
Hello Serge.
Serge: How are you Mary?
Goodbye.

Groovy way to dynamically invoke a static method

I know in Groovy you can invoke a method on a class/object using a string. For example:
Foo."get"(1)
/* or */
String meth = "get"
Foo."$meth"(1)
Is there a way to do this with the class? I have the name of the class as a string and would like to be able to dynamically invoke that class. For example, looking to do something like:
String clazz = "Foo"
"$clazz".get(1)
I think I'm missing something really obvious, just am not able to figure it out.
As suggested by Guillaume Laforge on Groovy ML,
("Foo" as Class).get(i)
would give the same result.
I've tested with this code:
def name = "java.lang.Integer"
def s = ("$name" as Class).parseInt("10")
println s
Try this:
def cl = Class.forName("org.package.Foo")
cl.get(1)
A little bit longer but should work.
If you want to create "switch"-like code for static methods, I suggest to instantiate the classes (even if they have only static methods) and save the instances in a map. You can then use
map[name].get(1)
to select one of them.
[EDIT] "$name" is a GString and as such a valid statement. "$name".foo() means "call the method foo() of the class GString.
[EDIT2] When using a web container (like Grails), you have to specify the classloader. There are two options:
Class.forName("com.acme.MyClass", true, Thread.currentThread().contextClassLoader)
or
Class.forName("com.acme.MyClass", true, getClass().classLoader)
The first option will work only in a web context, the second approach also works for unit tests. It depends on the fact that you can usually use the same classloader as the class which invokes forName().
If you have problems, then use the first option and set the contextClassLoader in your unit test:
def orig = Thread.currentThread().contextClassLoader
try {
Thread.currentThread().contextClassLoader = getClass().classLoader
... test ...
} finally {
Thread.currentThread().contextClassLoader = orig
}
An augmentation to Chanwit's answer illustrating creation of an instance:
def dateClass = 'java.util.Date' as Class
def date = dateClass.newInstance()
println date
Here's another way
import org.codehaus.groovy.grails.commons.ApplicationHolder as AH
def target = application.domainClasses.find{it.name == 'ClassName'}
target.clazz.invokeMethod("Method",args)
With this you don't need to specify the package name. Be careful though if you have the same class name in two different packages.
Melix on Groovy ML pointed me in the "right" direction on dynamic class method invokation awhile back, quite useful:
// define in script (not object) scope
def loader = this.getClass().getClassLoader()
// place this in some MetaUtils class, invoked on app startup
String.metaClass.toClass = {
def classPath = getPath(delegate) // your method logic to determine 'path.to.class'
Class.forName(classPath, true, this.loader)
}
// then, anywhere in your app
"Foo".toClass().bar()
You could create another string metaClass method to create instances as well, refactoring as appropriate:
String.metaClass.toObject = {
def classPath = getPath(delegate)
Class.forName(classPath, true, this.loader).newInstance()
}
Groovy is pure fun ;--)
I'm running version 1.8.8 groovy... and the simple example works.
Import my.Foo
def myFx="myMethodToCall"
def myArg = 12
Foo."$myFx"(myArg)
Calls Foo.myMethodToCall(12) as expected and desired. I don't know if this has always been the case though.

Resources