In Drools, I created a drl file with the following content:
import com.myorg.model.Child;
import com.myorg.model.ExportData;
import function com.myorg.utils.getParentSalary;
rule "Classification childSpecialSchool"
when
$exportData : ExportData( )
$child : Child($parentSalary: getParentSalary($exportData, this), $parentSalary > 4000, _age < 15 && _age > 10)
then
$child.set_classification("childSpecialSchool");
end
In my Java application, I fill WorkingObjects with a list of children and an exportData containing a lot of information.
Collection<Object> objectsForRules;
objectsForRules.addAll(listChildren);
objectsForRules.add(exportData);
ExportData is a class with a field mapSalary
class ExportData {
Map<Child, Double> mapSalary;
}
When I validate the drl file I have the following error [KBase: defaultKieBase]: Variables can not be used inside bindings. Variable [$exportData] is being used in binding 'getParentSalary($exportData, this)'.
I saw 2 other topics with same error but answers didn't help.
Someone can help? I am using Drools 7.37.
You can't reference $exportData there.
You could, as a workaround, move the parent salary logic outside of the Child reference like this:
rule "Classification childSpecialSchool"
when
$exportData : ExportData( )
$child : Child(_age < 15, _age > 10)
$parentSalary: Integer( this > 4000 ) from getParentSalary($exportData, $child)
then
$child.set_classification("childSpecialSchool");
end
(I'm assuming that the result of getParentSalary is an Integer but you can adjust as needed.)
Related
I am new to Drools and am having a tough time writing rules
Here is my data structure :
public class Premium{
private List<InsuranceType> insuranceTypes;
}
public class InsuranceType {
private String name;
}
So a Premium object will contain a List of Insurance types and I need to check if any of the insurance types has a name of "TPD"
Have tried the following :
rule "rule#3"
when
$fact:Premium($insuranceTypes : InsuranceType(name == 'TPD'))
then
System.out.println("Error");
end
However app server fails to start with the following error:
2021-11-30 12:16:37.004 ERROR 23500 --- [ main]
o.d.c.k.builder.impl.AbstractKieModule : Unable to build KieBaseModel:defaultKieBase
Unable to Analyse Expression InsuranceType(name == "TPD"):
[Error: unable to resolve method using strict-mode: com.xyz.Premium.name()]
[Near : {... InsuranceType(name == "TPD") ....}]
^
[Line: 29, Column: 5] : [Rule name='rule#3']
Unable to analyze expression 'InsuranceType(name == "TPD")' : [Rule name='rule#3']
Field Reader does not exist for declaration '$insuranceTypes' in '$insuranceTypes :
InsuranceType(name == "TPD")' in the rule 'rule#3' : [Rule name='rule#3']
I'm going to assume there's a public getName method on the InsuranceType class, and a public getInsuranceTypes method on the Premium class. If either of those isn't true, you need to add either getters or make those properties public.
Your rule was pretty close. However the problem you have is that insuranceTypes is a list but you were treating it as an object.
You have several options here, depending on your needs. However I'd go with the simplest, which is this:
rule "Example"
when
Premium( $insuranceTypes: insuranceTypes )
exists( InsuranceType( name == "TPD" ) from $insuranceTypes )
then
System.out.println("Error");
end
In the first line, I get the insurance types and assign them to the variable $insuranceTypes. This variable is now the list of types.
Then in the second line, I assert that there exists at least one InsuranceType in the list that has the name "TPD".
Note that Drools also has a memberOf operator, and a contains operator, which come in useful when working with lists and other iterable collections. These are inverses of each other, eg. you'd do Example( foo memberOf $someList ) or Example( myList contains $something ).
How can I generate something like this in my rule using PackageDescr ?
$var: Number (doubleValue > 100 ) from myPredefinedFunction()
I tried the following :
PatternDescr pt = new PatternDescr("Number","$var");
RelationalExprDescr ex = new RelationalExprDescr(">", false, null, new ExprConstraintDescr("myPredefinedFunction()"), new ExprConstraintDescr("100"));
pt.addConstraint(ex);
but this is what I get :
$var : Number( myPredefinedFunction() > 100 )
You're trying to set the myPredefinedFuntion() as a constraint. Constraints are the part of the drools declaration between the parentheses, eg. MyObject( foo == "bar" ) ... the foo == "bar" is a constraint.
Instead you need to set the source using the setSource method. This is the 'from' part of the declaration. This method takes a instance of a PatternSourceDescr subclass -- likely a FromDescr for this particular scenario.
(Alternatively, you might need setResource instead of setSource. The problem with using internal-only APIs is that they're not documented and subject to change without notice. I strongly suggest not going down this route.)
I want use the 'in' keyword in 'eval' function in which I am getting error that 'in' is not recognized by drools. So I have multiple values which I want check against a particular fact's attribute
when
$person : Person(PIN in ("123","456","789"))
then
//do something
end
//Like this I want use it in eval
when
$person : Person()
eval($person.PIN in ("123","456","789"))
then
//do something
end
But it is showing compile time error.
is there any other way to do it.
Edited
So I have some conditions in Decision Table where I want to use eval because other ways are not helpful in my scenario, below snapshot will Explain
SnapShot 1: Decision Table without eval()
SnapShot 2: Decision Table with eval()
Issue in first snapshot:
When compiling the spreadsheet the condition goes to the second lines object like below code : this is how it gets interpreted
when
personMap : PersonMap ()
basicEligiblePerson : Person( personalAddress.PIN in ($param) ) from
personMap.AddressesList
addresses : Address() from basicEligiblePerson.AddressesList
personalAddress : PersonalAddress() from addresses.PersonalAddress
then
basicEligiblePerson.setEligibility(true);
end
Issue in second snapshot :
When compiling this spreadsheet the condition goes to eval() function but 'in' keyword does not work in eval().
when
personMap : PersonMap ()
basicEligiblePerson : Person( personalAddress.PIN in ($param) ) from
personMap.AddressesList
addresses : Address() from basicEligiblePerson.AddressesList
personalAddress : PersonalAddress() from addresses.PersonalAddress
eval( personalAddress.PIN in ($param) )
then
basicEligiblePerson.setEligibility(true);
end
what should I do?
First sample given in your question is sufficient for the validation. You don't need to use eval.
when
$person : Person(PIN in ("123","456","789"))
then
//do something
end
If your requirement is to set eligibility to true for a given set of PINs, then you don't really need a decision table. I don't completely understand your POJO structure, so if Person class has a member variable addressList and AddressList class has a member personalAddress which has the member variable pin, you can achieve the results using the following rule. Please note that the nested fields are referred using the member variable names, not the class names. Also when you access the nested elements, if any of the elements can be null, please add the null check as well to avoid null pointer exceptions.
when
$basicEligiblePerson : Person( addressesList.personalAddress.pin in ("1234", "4567") )
then
$basicEligiblePerson.setEligibility(true);
end
I am still trying to wrap my head around some basics of drools.
One thing I don't understand is to what level I have to explicitly add facts into a session vs. declaring something as a fact or (how is it different?) as a variable within a rule.
Question: Do I always have to explicitly add a fact or does drools see a declared variable somehow as a fact as well?
Say I have this simplified hiearchy:
public class Container {
private Collection<Element> elements;
// other stuff
}
public class Element {
private SubElement subElement;
// other stuff
}
public class SubElement {
private code;
// other stuff
}
Now I want to find/match a container that contains Elements with a SubElement of a given code.
This is a rule I created:
global Collection qualifyingCodes;
rule "container rule"
when
// find a container
$container : Container( $elements : elements )
// where there is an element within its elements list
$element : Element(this memberOf $elements)
// that has a sub element
$subElement : SubElement(this == $element.subElement)
// for which the code is in the (global) list of codes
eval(qualifyingCodes.contains($subElement.code))
then
....
Now it seems to work fine, but only if I add the container, the elements and subelements separately as facts into my session.
I was hoping to be able to just add the container object as a fact and due to the "declared" $elements/$element/$subElement drools would understand those as facts as well.
Is there a way to do this (or do I always have to flatten out my data structures and add those as separate facts)?
Thanks for any advice!
POJOs inserted as facts can be matched with a pattern without the need for establishing a context (as your rule illustrates very well).
It is, however, possible to create a context where a limited set of POJOs that may or may not have been inserted as facts can be matched by a pattern: via the from phrase. Here's essentially the same rule, but without Element and SubElement being facts:
rule "from rule"
when
// find a container
$container : Container( $elements : elements )
// where there is an element within its elements list
$element : Element($subEl: subElement ) from $elements
// that has a sub element
$subElement : SubElement(this == $element.subElement, data == "DEF")
from $subEl;
then
System.out.println( "found Container " + $container +
" for " + $subElement.getData());
end
But, besides being visible in a general context, insertion as a fact has another consequence as well: facts can be modified with the engine being aware of that modification. In the "from rule", a modify of $subElement isn't possible, and so any modification setting that SubElement's data to "DEF" will not make this rule fire (not even if you insert the SubElement POJO).
For completeness sake: reasoning with a hierarchy of objects (composition) is also simplified by adding the references to the parent object, e.g.
rule "linkage rule"
when
// a sub element GHI
$subElement : SubElement( data == "GHI", $element: element)
then
System.out.println( "found Container " + $element.getContainer() +
" for " + $subElement.getData());
end
I have a rule similar to the following. I need to assign the value returned by my matcher to a handler to be used elsewhere. How can I do that? Thank you.
package a.b.c
import java.util.List;
import a.b.Matcher;
import a.b.Container;
import a.b.TestObject1;
import a.b.TestObject2;
global Matcher myMatcher;
global Container container;
when
$x1: TestObject1(
$x1_1 : id, (id in ("11", "16", "140"))
)
$x2: TestObject2(
$x2_2: id, myMatcher.match(id, container.getContainer(100)) != null
)
then
//print.
end
AFAIK there is no straightforward way to do that.
2 options are:
Call myMatcher.match() again in the RHS of the rule. This could be problematic if the match() method execution is expensive.
Create a different rule calculating the match() value for all your TestObject2 facts:
rule 'Calculate match value'
$t: TestObject2()
when
TestObject2Match m = new TestObject2Match($t); // -> you must define this class
m.setMatchValue(myMatcher.match($t.getId(), container.getContainer(100)));
insertLogical(m);
then
Now, the rule you previously had must be rewritten as follows:
rule '...'
$x1: TestObject1(
$x1_1 : id, (id in ("11", "16", "140"))
)
$m: TestObject2Match(matchValue != null)
when
//print $m.getMatchValue()
then
Hope it helps,