Using from {x}.field in DSL in Drools - dsl

I have the following Drools DSL "sentence":
[when]The field {field} in the module {module} contains value {value}=$a : {module} ( {field} != null)
String( this.equalsIgnoreCase("{value}") ) from $a.{field}
where the field is a Set of Strings.
Now, if I have two of these rules, it obviously won't work as the variable $a occurs twice. So I wanted to improve the rule to make the variable, well, variable:
[when]The field {field} in the module {module} contains value {value} as {a}={a} : {module} ( {field} != null)
String( this.equalsIgnoreCase("{value}") ) from {a}.{field}
This doesn't work, I can't use the part {a}., that breaks.
So, my questions are: Is there either a way to rewrite the rules or a way to allow the {variable}. notation to work? Alternatively, is there a contains operator which works case insensitive?

After I subscribed to the Drools-Users mailing list, I got an answer:
http://drools.46999.n3.nabble.com/rules-users-Using-from-x-field-in-DSL-tt4017872.html
Summary: Bug in DSL parser, as a workaround add an extra letter after the variable on the RHS: ... as {a}={a}x (...) ... from {a}x.{field}

Related

Match Operator prettier syntax in Groovy

I'm very new to groovy (here working on Jenkinsfile)
One of my coworkers uses a Match Operator to check a condition. But I find it not readable and hard to maintain.
Original Match Operator:
PROJECT_NAME = 'projectA' // User Input from Jenkins params normaly
if ( "${PROJECT_NAME}" ==~ /projectA|projectB|projectC|projectD/) { // The real line is 300 Char long
// Do stuff
}
There is 15 projects in total, i've shorten up the line because it was too long. So every time he needs to add a project name, he appends at the start or end of his regex.
Also, those project name are in a list before.
projects = ['projectA',
'projectB',
'projectC',
'projectD']
Could there be a way to use this list to build the regex?
Here is what I tried:
string_regex = "/"
for (project in projects) {
string_regex = string_regex + project + "|"
}
string_regex = string_regex.substring(0, string_regex.length() - 1)
string_regex = string_regex + "/"
print "${string_regex}\n"
if ("${PROJECT_NAME}" ==~ string_regex) {
print "Well Done you did it\n"
// Do stuff
}
But saddly it doesn't seems to work, since I'm using a string?
EDIT: I found out that I could use the contains method from a list in Groovy. In my case, it fixes my original problem. But I'm still curious on how to build such regex with strings.
if (projects.contains(PROJECT_NAME)) {
// Do stuff
}
You can join your projects and then turn the string into a regexp via Pattern.compile(). For good measure use Pattern.quote() to safe-guard against chars in your project names with "meaning" in regexp.
import java.util.regex.Pattern
def projects = ['projectA',
'projectB',
'projectC',
'projectD']
def re = Pattern.compile(projects.collect{ Pattern.quote it }.join("|"))
['projectA', 'projectX'].each{
println it ==~ re
}
// -> true
// -> false
For what it's worth, I came to like the Groovy matching operators for their compact syntax. If you learn about them and practice for a short bit, you will probably get to like them, too.
Regardless, for a simple check, on whether a String is part of a list, there is a much simpler way in Groovy than using full blown regexp : the in operator, e.g.:
def projects = ['projectA',
'projectB',
'projectC',
'projectD']
['projectA', 'projectX'].each {
println "${it} is ${it in projects ? 'IN' : 'NOT IN'} the project list"
}
which yields e.g.:
projectA is IN the project list
projectX is NOT IN the project list
More info on that operator and many other aspects of the Groovy language from the always excellent MrHaki here
Of course, if you need to account for case differences, etc... you have to massage the code a bit, but at some point, a regexp might be warranted.
If you have already an collection, you should nearly always use an collection operator; E.g. replace
if ( "${PROJECT_NAME}" ==~ /projectA|projectB|projectC|projectD/) {
with:
if (PROJECT_NAME in projects) {
Much easier to read and understand, no? 😉

Expected string literal in condition express in Jenkins pipeline

I am using ?: to determine the build agent of Jenkins shared library groovy script like this:
def call(String type, Map map) {
if (type == "gradle") {
pipeline {
agent "${map.agent == null}" ? "any" : "${map.agent}"
}
}
}
but it gives me the following error:
org.jenkinsci.plugins.workflow.cps.CpsCompilationErrorsException: startup failed:
/Users/dabaidabai/.jenkins/jobs/soa-robot/builds/154/libs/pipeline-shared-library/vars/ci.groovy: 6: Expected string literal # line 6, column 42.
agent "${map.agent == null}" ? "any" :
^
/Users/dabaidabai/.jenkins/jobs/soa-robot/builds/154/libs/pipeline-shared-library/vars/ci.groovy: 6: Only "agent none", "agent any" or "agent {...}" are allowed. # line 6, column 13.
agent "${map.agent == null}" ? "any" : "${map.agent}"
^
/Users/dabaidabai/.jenkins/jobs/soa-robot/builds/154/libs/pipeline-shared-library/vars/ci.groovy: 6: No agent type specified. Must be one of [any, docker, dockerfile, label, none] # line 6, column 13.
agent "${map.agent == null}" ? "any" : "${map.agent}"
What am I doing wrong?
This error is thrown by the pipeline syntax validator that runs before your pipeline code gets executed. The reason you see this error is the following:
Only "agent none", "agent any" or "agent {...}" are allowed. # line 6, column 13.
This is the constraint for the label section. It means that the following values are valid:
agent any
agent none
agent "constant string value here"
agent { ... }
When you pass something like:
agent "${map.agent ?: 'any'}
agent(map.agent ?: 'any')
you are getting Expected string literal because any form of an expression is not allowed in this place, including interpolated GStrings of any form.
Solution
There is a way to define pipeline agent dynamically however. All you have to do is to use a closure block with the label set to either expression or empty string (an equivalent of agent any in this case.)
pipeline {
agent {
label map.agent ?: ''
}
stages {
...
}
}
The label section allows you to use any expression, so map.agent is a valid construction here. Just remember to use an empty string instead of "any" - otherwise Jenkins will search for a node labeled as "any".
Don't use string replacments everyhwhere:
agent(map.agent==null ? "any" : map.agent)
Or get groovy:
agent(map.agent?:"any")
The actual problem is most likely the "ternary operator" battling against the "parens are maybe optional"-rule.
It seems to me that agent is a method taking a string argument and the way your code is written is ambiguous. Try surrounding the argument expression with parens:
agent(map.agent == null ? "any" : "${map.agent}")
I removed the quotes around map.agent == null as they seemed extraneous.
Also this could probably be rewritten using the groovy "elvis operator" (:?) as:
agent(map.agent ?: "any")
Which essentially means "use map.agent if it has a value, otherwise use 'any'". "If it has a value" is in this context being defined using groovy truth where both empty string and null represent "no value".

Switching on String Value Yields Unexpected Results in Groovy

I am working in a groovy/grails set up and am having some trouble trying to execute a switch statement on a String value.
Basically, I am looping through the attribute names in a webservice response to see if they match pre-defined mappings that are configured on a per user basis. If they have established a mapping on that field, I pull the value out of the response and use it elsewhere.
The code looks something like this:
switch(attributeName)
{
case {attributeName} :
log.info("Currently switching on value... ${attributeName}")
case user.getFirstNameMapping():
model.user.userInfo.firstName = attributeValue
break
case user.getLastNameMapping():
model.user.userInfo.lastName = attributeValue
break
case user.getAuthenticationKeyMapping():
model.authenticationValue = attributeValue
break
case user.getEmailMapping():
model.email = attributeValue.toLowerCase()
break
}
The value being switched on (attributeName) is of type String, and the getter methods for the user instance also return type String.
Based on my research and understanding of the Groovy language, switching on an Object such as a String should end up using String.equals() to make the comparison. The result, however, is that it is matching on the user.getFirstNameMapping() case every time, and repeatedly overwriting the value in the model; therefore, the last value that comes back in the response is what ends up saved, and none of the other values are saved.
What's interesting is that if I use an if/else structure and do something like this:
if(attributeName.equals(user.getFirstNameMapping())
{
...
}
It works fine every time. I've verified through logging that it's not something silly like extra whitespace or a capitalization issue. I've also tried changing things around to run the switch by default and explicitly compare the attributeName in the case like this:
switch(true)
{
case {attributeName} :
log.info("Currently switching on value... ${attributeName}")
case {user.getFirstNameMapping().equals(attributeName)}:
model.user.userInfo.firstName = attributeValue
break
case {user.getLastNameMapping().equals(attributeName)}:
model.user.userInfo.lastName = attributeValue
break
case {user.getAuthenticationKeyMapping().equals(attributeName)}:
model.authenticationValue = attributeValue
break
case {user.getEmailMapping().equals(attributeName)}:
model.email = attributeValue.toLowerCase()
break
}
And it still fails to meet my expectations in the exact same way. So, I'm wondering why this is the behavior when the switch statement should simply be using .equals() to compare the strings, and when I explicitly compare them in an if/else using .equals(), it works as expected.
The issue is in your switch case.
Have a look here :-
case {attributeName} :
log.info("Currently switching on value... ${attributeName}")
case user.getFirstNameMapping():
model.user.userInfo.firstName = attributeValue
break
As you can see your these two cases will run every time because the switch condition is :-
switch(attributeName)
So the first one will get match and will run until it encounters break; which is at after case 2 i.e. case user.getFirstNameMapping(): so i would suggest you to print the value of {attributeName} before the swtich starts.
Hope that will help you.
Thanks
I don't know exactly what's your issue, but the case statement works just fine, even with methods. See my example
String something = "Foo"
class User {
String firstName
String lastName
}
User u = new User(firstName: 'Something', lastName:'Foo')
switch(something) {
case u.getFirstName():
println "firstName: ${u.firstName}"
break;
case u.getLastName():
println "lastName: ${u.lastName}"
break;
default:
println "nothing..."
}
This code will print lastName as expected.
​

how to handle conditionally existing components in action code?

This is another problem I am facing while migrating from antlr3 to antlr4. This problem is with the java action code for handling conditional components of rules. One example is shown below.
The following grammar+code worked in antlr3. Here, if the unary operator is not present, then a value of '0' is returned, and the java code checks for this value and takes appropriate action.
exprUnary returns [Expr e]
: (unaryOp)? e1=exprAtom
{if($unaryOp.i==0) $e = $e1.e;
else $e = new ExprUnary($unaryOp.i, $e1.e);
}
;
unaryOp returns [int i]
: '-' {$i = 1;}
| '~' {$i = 2;}
;
In antlr4, this code results in a null pointer exception during a run, because 'unaryOp' is 'null' if it is not present. But if I change the code like below, then antlr generation itself reports an error:
if($unaryOp==null) ...
java org.antlr.v4.Tool try.g4
error(67): missing attribute access on rule reference 'unaryOp' in '$unaryOp'
How should the action be coded for antlr4?
Another example of this situation is in if-then-[else] - here $s2 is null in antlr4:
ifStmt returns [Stmt s]
: 'if' '(' e=cond ')' s1=stmt ('else' s2=stmt)?
{$s = new StmtIf($e.e, $s1.s, $s2.s);}
;
NOTE: question 16392152 provides a solution to this question with listeners, but I am not using listeners, my requirement is for this to be handled in the action code.
There are at least two potential ways to correct this:
The "ANTLR 4" way to do it is to create a listener or visitor instead of placing the Java code inside of actions embedded in the grammar itself. This is the only way I would even consider solving the problem in my own grammars.
If you still use an embedded action, the most efficient way to check if the item exists or not is to access the ctx property, e.g. $unaryOp.ctx. This property resolves to the UnaryOpContext you were assuming would be accessible by $unaryOp by itself.
ANTLR expects you access an attribute. Try its text attribute instead: $unaryOp.text==null

Groovy - How can I compare part of a list to a string

I have a list which contains two types of text. One type is used for authorization while other type is used for all other purposes.
The type used for authorization always uses the same text + some code after it.
I would like to compare content of these two types of text and separate them based on content.
My idea is to look for pattern in authorization type and if it matches the pattern then this would be marked as authorization, otherwise it would be marked as "other".
I researched about comparison of patterns in Groovy, but all variations I tried did not work for me. Here is the part which should do the comparison, I am obviously doing something wrong but I don't know what.
jdbcOperations.queryForList(sql).collect { row ->
if(assert (row['MSG'] ==~ /token/)){
mark as authorization
}
else{
mark as other
}
}
Sorry for the vague code, I can not share more than this.
I think you just missing the match for the rest of the text, since you are looking only for the first part to match.
assert ("abc" ==~ /abc/) == true
assert ("abcdefg" ==~ /abc/) == false
assert ("abcdefg" ==~ /abc(.*)/) == true // <--- This can also be made more complicated

Resources