I am trying to dynamically create an XML file with Groovy. I'm pleased with the simplicity everything works, but i am having a hard time understanding the whole mechanism behind closures and delegates. While it appears to be easy to add properties and child nodes with a fixed name, adding a node with a dynamic name appears to be a special case.
My use case is creating a _rep_policy file, which can be used in CQ5.
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
jcr:primaryType="rep:ACL">
<allow
jcr:primaryType="rep:GrantACE"
rep:principalName="administrators"
rep:privileges="{Name}[jcr:all]"/>
<allow0
jcr:primaryType="rep:GrantACE"
rep:principalName="contributor"
rep:privileges="{Name}[jcr:read]"/>
</jcr:root>
Processing the collections works fine, but generating the name ...
import groovy.xml.StreamingMarkupBuilder
import groovy.xml.XmlUtil
def _rep_policy_files = [
'/content': [ // the path
'deny': [ // permission
'jcr:read': [ // action
'a1', 'b2']], // groups
'allow': [
'jcr:read, jcr:write': [
'c2']
]
]
]
def getNodeName(n, i) {
(i == 0) ? n : n + (i - 1)
}
_rep_policy_files.each {
path, permissions ->
def builder = new StreamingMarkupBuilder();
builder.encoding = "UTF-8";
def p = builder.bind {
mkp.xmlDeclaration()
namespaces << [
jcr: 'http://www.jcp.org/jcr/1.0',
rep: 'internal'
]
'jcr:root'('jcr:primaryType': 'rep:ACL') {
permissions.each {
permission, actions ->
actions.each {
action, groups ->
groups.eachWithIndex {
group, index ->
def nodeName = getNodeName(permission, index)
"$nodeName"(
'jcr:primaryType': 'rep:GrantACE',
'rep:principalName': "$group",
'rep:privileges': "{Name}[$action]")
}
}
}
}
}
print(XmlUtil.serialize(p))
}
Is this something (or similar) that you are looking for?
'jcr:root'('jcr:primaryType': 'rep:ACL') {
_rep_policy_files['/content'].each {k, v ->
if(k == 'allow')
"$k"('jcr:primaryType': 'rep:GrantACE',
'rep:principalName': 'administrators',
'rep:privileges': "$v" ){}
if(k == 'deny')
"$k"('jcr:primaryType': 'rep:GrantACE',
'rep:principalName': 'contributor',
'rep:privileges': "$v" ){}
}
}
The resultant xml shown in question cannot be apprehended properly with what you have in _rep_policy_files.
Related
I am new to Groovy and working on a device handler for my Smartthing Hub which is written in Groovy. I am having trouble parsing a string.
def parseDescriptionAsMap(description) {
println "description: '${description}"
def test = description.split(",")
println "test: '${test}"
test.inject([:]) { map, param ->
def nameAndValue = param.split(":")
println "nameAndValue: ${nameAndValue}"
if(map)
{
println "map is NOT NULL"
map.put(nameAndValue[0].trim(),nameAndValue[1].trim())
}
else
{
println "map is NULL!"
}
}
}
Output:
description: 'index:17, mac:AAA, ip:BBB, port:0058, requestId:ce6598b2-fe8b-463d-bdf3-01ec35055f7a, tempImageKey:ba416127-14e3-4c7b-8f1f-5b4d633102e5
test: '[index:17, mac:AAA, ip:BBB, port:0058, requestId:ce6598b2-fe8b-463d-bdf3-01ec35055f7a, tempImageKey:ba416127-14e3-4c7b-8f1f-5b4d633102e5]
nameAndValue: [index, 17]
nameAndValue: [ mac, AAA]
map is NULL!
nameAndValue: [ ip, BBB]
map is NULL!
map is NULL!
nameAndValue: [ port, 0058]
nameAndValue: [ requestId, ce6598b2-fe8b-463d-bdf3-01ec35055f7a]
Two questions:
1. Why is the variable, map, null?
2. Why is the function not printing the nameAndValue->'tempImageKey' info?
map cannot be null, if(map) checks if it's null or empty...and it will not be null in this situation (so long as you follow #2)
You need to return map from the inject closure, so that is can be aggregated.
test.inject([:]) { map, param ->
def nameAndValue = param.split(":")
println "nameAndValue: ${nameAndValue}"
map.put(nameAndValue[0].trim(),nameAndValue[1].trim())
map
}
A simpler version of what you're trying would be:
description.split(',')*.trim()*.tokenize(':').collectEntries()
I am new to ConfigObject. I am planning to use it for storing the results of an analytics job. However occasionally I might need to add new configuration field, and I am having a difficult time adding variables to ConfigObject. So I decided to add a map instead. But that is not getting formatted correctly, resulting in error while reading back. I am not sure how to proceed..
config.groovy file
-------------------
"serverX" {
var {
low=-12.89
maybe_low=1.65
maybe_high=40.45
high=55
}
}
rest of Script
----------------------
ConfigObject conf = new ConfigSlurper("development").parse(new File("config.groovy").toURI().toURL());
System.out.println(conf);
// New object to add
def var_stuff = ['low' : 0] + ['maybe_low' : 0] + ['maybe_high' : 0] + ['high' : 0]
def var_object = ['var' : var_stuff]
def final_var_object = ['ServerY' : var_object]
def new_config_object = final_var_object as ConfigObject
conf.merge(new_config_object)
def file1 = new File('newconfig.groovy')
file1.withWriter('UTF-8') { writer ->
conf.writeTo(writer)
}
But the result turns out to be
serverX {
var {
low=-12.89
maybe_low=1.65
maybe_high=40.45
high=55
}
}
serverY=["var":["low":0, "maybe_low":0, "maybe_high":0, "high":0]]
I did consider doing the following
ConfigObject conf_new = new ConfigObject();
conf_new."ServerY".tukey.low = 0
conf_new."ServerY".tukey.maybe_low = 0
conf_new."ServerY".tukey.maybe_high = 0
conf.merge(conf_new)
Which works great. But since "ServerY" is a variable for me, I cannot write such manual statements.
Any hints?
EDIT: 5th March 2016
As per Emmanuel's recommendation, i tried the following
def server = 'ServerY'
def more = [
(server): [
var: [
low: 0,
maybe_low: 0,
maybe_high: 0,
high: 0
]
]
]
conf.putAll(more)
def obj1_temp = conf as Map
System.out.println(obj1_temp)
/* which looks like this:
{ServerX={var={low=-12.89, maybe_low=1.65, maybe_high=40.45, high=55}}, ServerY={var={low=0, maybe_low=0, maybe_high=0, high=0}}}
*/
def file1 = new File('app/tasks/newconfig.groovy')
file1.withWriter('UTF-8') { writer ->
conf.writeTo(writer)
}
However output in the file is still similar as before :-(
ServerX {
var {
low=-12.89
maybe_low=1.65
maybe_high=40.45
high=55
}
}
ServerY=["var":["low":0, "maybe_low":0, "maybe_high":0, "high":0]]
which doesn't parse properly when reading back.
You can use putMap() to add the Map entries to the ConfigObject.
import groovy.util.ConfigSlurper
def content = '''
"serverX" {
var {
low=-12.89
maybe_low=1.65
maybe_high=40.45
high=55
}
}
'''
def conf = new ConfigSlurper("development").parse(content) // Using a String for demonstration purposes
def server = 'ServerY'
def more = [
(server): [
var: [
low: 0,
maybe_low: 0,
maybe_high: 0,
high: 0
]
]
]
conf.putAll(more)
// A test showing it works.
assert conf as Map == [
serverX:[
var:[
low:-12.89,
maybe_low:1.65,
maybe_high:40.45,
high:55]
],
ServerY:[
var:[
low:0,
maybe_low:0,
maybe_high:0,
high:0
]
]
]
Update
So, putAll() didn't work as I expected. Here's the solution:
import groovy.util.ConfigSlurper
import groovy.util.ConfigObject
def content = '''
"serverX" {
var {
low=-12.89
maybe_low=1.65
maybe_high=40.45
high=55
}
}
'''
def mergeConfig = { configObject, server, map ->
configObject.merge new ConfigObject().with {
def var = getProperty(server).getProperty('var')
map.each { key, value ->
var.put(key, value)
}
delegate
}
configObject
}
def conf = new ConfigSlurper("development").parse(content) // Using a String for demonstration purposes
conf = mergeConfig(conf, 'ServerY', [low: 0, maybe_low: 0, maybe_high: 0, high: 0])
You can now call mergeConfig() continuously to merge configurations. In this example, mergeConfig() is a Closure, but it would work as a method also.
I would like to write a system groovy script which inspects the queued jobs in Jenkins, and extracts the build parameters (and build cause as a bonus) supplied as the job was scheduled. Ideas?
Specifically:
def q = Jenkins.instance.queue
q.items.each { println it.task.name }
retrieves the queued items. I can't for the life of me figure out where the build parameters live.
The closest I am getting is this:
def q = Jenkins.instance.queue
q.items.each {
println("${it.task.name}:")
it.task.properties.each { key, val ->
println(" ${key}=${val}")
}
}
This gets me this:
4.1.next-build-launcher:
com.sonyericsson.jenkins.plugins.bfa.model.ScannerJobProperty$ScannerJobPropertyDescriptor#b299407=com.sonyericsson.jenkins.plugins.bfa.model.ScannerJobProperty#5e04bfd7
com.chikli.hudson.plugin.naginator.NaginatorOptOutProperty$DescriptorImpl#40d04eaa=com.chikli.hudson.plugin.naginator.NaginatorOptOutProperty#16b308db
hudson.model.ParametersDefinitionProperty$DescriptorImpl#b744c43=hudson.mod el.ParametersDefinitionProperty#440a6d81
...
The params property of the queue element itself contains a string with the parameters in a property file format -- key=value with multiple parameters separated by newlines.
def q = Jenkins.instance.queue
q.items.each {
println("${it.task.name}:")
println("Parameters: ${it.params}")
}
yields:
dbacher params:
Parameters:
MyParameter=Hello world
BoolParameter=true
I'm no Groovy expert, but when exploring the Jenkins scripting interface, I've found the following functions to be very helpful:
def showProps(inst, prefix="Properties:") {
println prefix
for (prop in inst.properties) {
def pc = ""
if (prop.value != null) {
pc = prop.value.class
}
println(" $prop.key : $prop.value ($pc)")
}
}
def showMethods(inst, prefix="Methods:") {
println prefix
inst.metaClass.methods.name.unique().each {
println " $it"
}
}
The showProps function reveals that the queue element has another property named causes that you'll need to do some more decoding on:
causes : [hudson.model.Cause$UserIdCause#56af8f1c] (class java.util.Collections$UnmodifiableRandomAccessList)
I have a groovy list as below
def permissions = [
TESTENTITY.ADMINISTER,
TESTENTITY.CREATE,
TESTENTITY.DELETE,
TESTENTITY.READ,
TESTENTITY.UPDATE,
TESTBEAN.ADMINISTER,
TESTBEAN.CREATE,
TESTBEAN.DELETE,
TESTBEAN.READ,
TESTBEAN.UPDATE
]
Am trying to find all the elements in the list of format "TESTENTITY" with less code. Is it doable?
This is the way I interpret the question.
Not considering each element as a String but a collection of Enum.
enum TestEntity {
ADMINISTER, CREATE, DELETE, READ, UPDATE
}
enum TestBean {
ADMINISTER, CREATE, DELETE, READ, UPDATE, DONOTHING
}
def permissions = [
TestEntity.ADMINISTER, TestEntity.CREATE,
TestEntity.DELETE, TestEntity.READ, TestEntity.UPDATE,
TestBean.ADMINISTER, TestBean.CREATE,
TestBean.DELETE, TestBean.READ, TestBean.UPDATE, TestBean.DONOTHING
]
permissions.findAll { it in TestEntity }
Note:
DONOTHING is added to TestBean just to show the difference.
You can do something like this...
def matchingResults = permissions.findAll { perm ->
// perm will be one element from permissions.
// inspect it and return true if it should be
// include in the results.
}
If those are strings in the permissions list, that might look something like this...
def matchingResults = permissions.findAll { perm ->
perm.startsWith 'TESTENTITY'
}
Is that what you are looking for?
I have a tsv file in the form of "key \t value", and I need to read into a map. Currently i do it like this:
referenceFile.eachLine { line ->
def (name, reference) = line.split(/\t/)
referencesMap[name.toLowerCase()] = reference
}
Is there a shorter/nicer way to do it?
It's already quite short. Two answers I can think of:
First one avoids the creation of a temporary map object:
referenceFile.inject([:]) { map, line ->
def (name, reference) = line.split(/\t/)
map[name.toLowerCase()] = reference
map
}
Second one is more functional:
referenceFile.collect { it.split(/\t/) }.inject([:]) { map, val -> map[val[0].toLowerCase()] = val[1]; map }
The only other way I can think of doing it would be with an Iterator like you'd find in Commons IO:
#Grab( 'commons-io:commons-io:2.4' )
import org.apache.commons.io.FileUtils
referencesMap = FileUtils.lineIterator( referenceFile, 'UTF-8' )
.collectEntries { line ->
line.tokenize( '\t' ).with { k, v ->
[ (k.toLowerCase()): v ]
}
}
Or with a CSV parser:
#Grab('com.xlson.groovycsv:groovycsv:1.0')
import static com.xlson.groovycsv.CsvParser.parseCsv
referencesMap = referenceFile.withReader { r ->
parseCsv( [ separator:'\t', readFirstLine:true ], r ).collectEntries {
[ (it[ 0 ].toLowerCase()): it[ 1 ] ]
}
}
But neither of them are shorter, and not necessarily nicer either...
Though I prefer option 2 as it can handle cases such as:
"key\twith\ttabs"\tvalue
As it deals with quoted strings
This is the comment tim_yates added to melix's answer, and I think it's the shortest/clearest answer:
referenceFile.collect { it.tokenize( '\t' ) }.collectEntries { k, v -> [ k.toLowerCase(), v ] }