I have 5 fact types: BaseFact, AFact, BFact, CFact and DFact.
AFact, BFact, CFact and DFact all inherit from BaseFact.
I have some rules that run on BaseFacts, that I can no longer run on CFacts or DFacts.
What is the best way to modify my BaseFact rules so that they only run on BaseFacts, AFacts and BFacts.
Is there some sort of instanceOf function I can check, like the following?
rule "BaseRule"
when
fact : BaseFact(this instanceOf AFact || this instanceOf BFact)
...
then
...
end
Or do I need to split this rule into 2 new rules, for AFact and BFact?
Even if there is no instanceOf operator, there are several ways to achieve what you are looking for.
These are some ideas:
rule "BaseRule"
when
fact : BaseFact(class == AFact.class || == BFact.class)
...
then
//note that the variable fact is still of type BaseFact
...
end
A nastier version:
rule "BaseRule"
when
fact : BaseFact()
not CFact(this == fact)
not DFact(this == fact)
...
then
//note that the variable fact is still of type BaseFact
...
end
Or:
rule "BaseRule"
when
AFact() OR
BFact()
...
then
//note you can't bind a variable to AFact or BFact
...
end
If you only have 2 concrete types you want to match, then having 2 individual rules doesn't sound like a bad idea either.
Hope it helps,
Quick update: I was also able to use:
rule "MyRow"
dialect "mvel"
when
f1 : Wrapper( eval( nestedProperty#MyNestedClass ), ... )
then
...
end
Which is handy for testing classes of (nested) properties.
Related
I'm trying to work with lists in drools. I'm passing in a request which has a purchase list as part of it. I want to do several rules including checking if the size is correct, then if all elements are the same, if all purchases are authorized, ... I have the following code but I'm running into problems working with the list. Is this the right approach? Especially when checking for the size?
import com.rules.Purchase
import com.rules.PurchaseRequest
dialect "mvel"
global Boolean eligibleForRefund
rule "Check for list not equal to two elements" salience 10
when
PurchaseRequest(getPurchases != null, getPurchases.size() != 2)
then
drools.getKieRuntime().setGlobal("eligibleForRefund", false);
end
rule "Check for two purchases" salience 9
when:
$purchaseRequest: PurchaseRequest()
Number(intValue != 2) from accumulate(Purchase(getStatus() == "Approved") from $purchaseRequest.getPurchases(), count(1))
then
drools.getKieRuntime().setGlobal("eligibleForRefund", false);
end
rule "Check for the same purchases" salience 8
when:
$purchaseRequest: PurchaseRequest()
then
firstPurchase = $purchaseRequest.getPurchases().get(0).getCost();
hasAllElements = true;
for (Purchase purchase : $purchaseRequest.getPurchases()) {
if (purchase.getCost() != firstPurchase) {
hasAllElements = false;
}
}
drools.getKieRuntime().setGlobal("eligibleForRefund", hasAllElements);
end
Assuming that your class definition looks like this:
class PurchaseRequest {
private List<Purchase> purchases;
public List<Purchase> getPurchases() { return this.purchases; }
}
You should be pulling references out of the holder instead of constantly interacting with things via the getters. In other projects this helps with keep data consistent especially with shared resources. Recall that if you have a getter whose name matches the format getXyz, you can refer to it simply as xyz and drools will automagically map it to the getter function. This allows us to get the purchases via PurchaseRequest( $purchases: purchases ) since purchases will be mapped to getPurchases(). (Note that if purchases happened to be a public variable, it would have mapped to that first; but since it's private it falls back on the public getter that follows bean naming conventions.)
Second you use an accumulate in a very simple scenario where a collect would probably be more appropriate. Generally you'd use accumulate for more complicated "get things that look like this" sort of situations; but for simple matching, a collect works just as well.
The third rule needs the most work. You do not want to do this kind of business logic on the right hand side of your rule. There's a whole lot of ways you could go about checking that all the elements are the same -- if you've implemented equals/hashCode you could just shove everything into a set and confirm that the length of the set is still the length of the list; you could invert the rule to instead check for at least one item that's different; you could use accumulate or collect; ...
Finally --
Avoid saliences. They're bad design. Your rules should stand alone. You only need saliences here because your third rule sets both true and false. If instead you defaulted to true and then used the rules to override it to false, you could get away with having absolutely no saliences at all.
It's very unusual to use primitives for a global variable. I'm frankly not convinced that this will even work with a primitive. Globals work because the object is passed in by reference, and updated in the rules, and therefore the caller which retains the reference to the object will get the updated value. That doesn't work with primitives.
rule "Check for list not equal to two elements"
salience 1
when
PurchaseRequest($purchases: purchases != null)
List(size != 2) from $purchases
then
drools.getKieRuntime().setGlobal("eligibleForRefund", false);
end
rule "Check for two purchases"
salience 1
when:
PurchaseRequest( $purchases: purchases != null)
List( size != 2 ) from collect( Purchase(status == "Approved") from $purchases)
then
drools.getKieRuntime().setGlobal("eligibleForRefund", false);
end
// I've no idea what data type `getCost()` returns; I'm assuming "String"
rule "Check for the same purchases"
when:
PurchaseRequest($purchases: purchases != null)
// accumulate all of the costs into a set. if all costs are the same, set size = 1
$costs: Set() from accumulate( Purchase( $cost: cost ) from $purchases;
collectSet($cost))
then
drools.getKieRuntime().setGlobal("eligibleForRefund", $costs.size() == 1);
end
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.)
We have a situation where if rule fails, we need to show what condition is failed. For that I need to show LHS of a particular rule. How can we do that in drools6.5. I am using it in jbpm6.5. Please help.
import java.lang.Number;
rule "parent"
#author(rupesh)
dialect "mvel"
ruleflow-group "grp"
when
obj : Player( totalWinnings >= 10.0 )
then
System.out.println(drools.getRule().getMetaData());
System.out.println(drools.getRule().getMetaAttributes());
System.out.println(drools.getRule());
end
I am not able to get LHS in sysout.
I am assuming that you want to see the LHS values when a rule is not activated to understand why it happened.
To do that, create a function like this:
function boolean debug(double x) {
System.out.println("lhs_debug(): "+x);
return true;
}
and then modify the rule to use it, like this:
rule "parent"
when
obj : Player( debug(totalWinnings), totalWinnings >= 10.0 )
then
System.out.println("activated: "+obj.totalWinnings);
//do something
end
This will show you all attempts to activate the rule and will help you debug when it was not activated.
Please note that it will be very verbose and time consuming, so it should be removed after debugging.
I am trying to rewrite my drl from using regex to equalsIgnoreCase as I think its faster. I am not sure its faster though. However, drools doesn't like it for some reason and I get unknown error.
The one on top works, but the one using equalsIgnoreCase doesn't
rule "name"
salience 0
activation-group "flow"
dialect "mvel"
no-loop true
when
$vurderinger: Vurderinger(vurdering1909 != null &&
vurdering1909.verdi matches "(?i)^FOO$")
then
modify( $vurderinger ) { setVurdering1913(new DroolsType("SHOW")) }
end
rule "name"
salience 0
activation-group "flow"
dialect "mvel"
no-loop true
when
$vurderinger: Vurderinger(vurdering1909 != null &&
eval("FOO".equalsIgnoreCase(vurdering1909.verdi)))
then
modify( $vurderinger ) { setVurdering1913(new DroolsType("SHOW")) }
end
Can anyone spot the mistake?
Within eval, stick to Java: refer to bound variables, use getter.
when
$vurderinger: Vurderinger($v: vurdering1909 != null &&
eval("FOO".equalsIgnoreCase($v.getVerdi())))
then
Edit Not knowing the class definition, the error and the version, I advise using eval/Java, to be on the safe side, no matter what the Drools version is. For 6.3.0, you can omit eval, and it works.
when
$vurderinger: Vurderinger(vurdering1909 != null &&
"FOO".equalsIgnoreCase(vurdering1909.verdi))
then
I have given solution for decision table i.e:
javaObject.getRisk().equalsIgnoreCase("$param")
for drools rules
rule "Rule To Check String Contains"
when
Pojo(name.contains("Loans"))
then
System.out.println(drools.getRule().getName());
end
rule "Rule To Check String Equals"
when
Pojo(name.equals("Personal Loans, Insuarnce"))
then
System.out.println(drools.getRule().getName());
end
rule "Rule To Check String EqualsIgnoreCase"
when
Pojo(name.equalsIgnoreCase("Personal loans, insuarnce"))
then
System.out.println(drools.getRule().getName());
end```
I'm having an object as below:
class License{
private field1;
private field2;
private boolean active;
private String activeMessage;
private boolean processed = false;
//Getter and setter methods
}
What I'm trying to do is, based on the values of field1, and field2, I need to set the isActive flag and a corresponding message. However, if either the rule for field1 or field2 is fired, I need to stop the rules processing. That is, I need to execute only 1 successful rule.
I read on a post that doing ksession.fireAllRules(1) will solve this. But the fireAllRules() method is not available in Drools 6. I also tried putting a return; statement at the end of each rule. That didn't help me either.
Finally, I ended up adding an additional field to my object called processed. So whenever I execute any rule, I set the processed flag to true. And if the flag is already set, then I do not execute any rule. This is my rules file:
rule "Check field1"
when
$obj : License(getField1() == "abc" && isProcessed() == false)
then
System.out.println("isProcessed >>>>>> "+$obj.isProcessed());
$obj.setActive(true);
$order.setActiveMessage("...");
$order.setProcessed(true);
end
rule "Check field2"
when
$obj : License(getField2() == "def" && isProcessed() == false)
then
System.out.println("isProcessed >>>>>> "+$obj.isProcessed());
$obj.setActive(true);
$order.setActiveMessage("...");
$order.setProcessed(true);
end
However, I see that even now both my rules are being fired. When I try to print the value of isProcessed(), it says true, even though I enter the rule only if isProcessed() is false.
This is how I'm calling the drools engine:
kieService = KieServices.Factory.get();
kContainer = kieService.getKieClasspathContainer();
kSession = kContainer.newStatelessKieSession();
kSession.execute(licenseObj);
It is not just 2 rules, I have a lot of rules, so controlling the rules execution by changing the order of the rules in the drl file is not an option. What is happening here? How can I solve this problem? I am sort of new to Drools, so I might be missing something here.
Thanks.
Your question contains a number of errors.
It is definitely not true that fireAllRules has disappeared in Drools 6. You might have looked at the javadoc index, to find four (4!) overloaded versions of this method in package org.kie.api.runtime.rule in the interface StatefulRuleSession.
You might easily avoid the problem of firing just one out of two rules by combining the triggering constraint:
rule "Check field1 and field2"
when
$lic: License(getField1() == "abc" || getField2() == "def" )
//...
then
$lic.setXxx(...);
end
You complain that both of your rules fire in spite of setting the processed flag in the fact. Here you are missing a fundamental point (which is covered in the Drools reference manual), i.e., the necessity of notifying the Engine whenever you change fact data. You should have used modify on the right hand side of your rules.
But even that would not have been good enough. Whenever an update is made due to some properties, a constraint should be added to avoid running the update over and over again. You might have written:
rule "Check field1 and field2"
when
$lic: License(getField1() == "abc" || getField2() == "def",
! active )
//...
then
modify( $lic ){ setActive( true ) }
end
You might even write this in two distinct rules, one for each field, and only one of these rules will fire...