Working with lists in Drools Rules Engine - drools

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

Related

How to access common declared types from a function in Drools?

Here am having multiple Rules in a .drl file. They are updating 6 different JSONArray. These updates are happening in the "then" section of the Drools. Rather than having the similar logic inside the rule, am trying to write a function which will update the 6 JSONArray. How would that be possible ?
declare USER1_LIST
querysetUser1 :JSONArray
end
declare USER2_LIST
querysetUser2 :JSONArray
end
......
......
......
The initialization is happening in a "Set Up" rule with highest Salience -
rule "setUp"
salience 25
when
then
USER1_LIST user1_list = new USER1_LIST();
user1_list.setQuerysetUser1(new JSONArray());
insert(user1_list);
....
In the rule am using one of the list based on the logic -
rule "RULE_XYZ"
salience 5
when
USER1_LIST($querysetUser1 :querysetUser1)
...<Some code>
then
...<Some code>
$querysetUser1.add(...);
Here I want to perform the operation "$querysetUser1.add(...);" inside a function. These JSONArray are updated by different rules. So my aim is to move the logic of selecting the JSONArray and updating it in a function, so that only this function would be called from different rules.
Declared types can be accessed in declared functions. However, from your example, it sounds like you just need to be able to do work against the JSONArray objects contained within the declared types.
The function would look something like this:
function void addToQuerySet(JSONArray querySet, JSONObject object) {// I have guessed the type of 'object'
querySet.add(object);
}
And you would invoke it from the then clause:
rule "RULE_XYZ"
when
USER1_LIST($querysetUser1 :querysetUser1)
// ...<Some code>
someObject: JSONObject() // the thing to insert
then
addToQuerySet($querysetUser1, someObject);
end
What you cannot do is write a generic method that will take any of your USER1_LIST, USER2_LIST, etc declared types because they're all unique and unrelated types, even if they all share the same structure.
Note that the fact that your declared types sharing the same structure is indicative of poor design -- it is likely they should all be instances of a single type, with somet other way of identifying which user it belongs to. For example, something like this:
declare UserList
querySet: JSONArray
userNum: int
end
Where userNum would indicate which user this is for. Then you could write a function that takes a UserList and a JSONObject (or whatever the type is of that thing is you're adding to the query set) like so:
function void addToQuerySet(UserList userList, JSONObject object) {
userList.getQuerySet().add(object);
}
And invoke it in a then clause like so:
rule "RULE XYZ version 2"
when
$user1List: UserList( userNum == 1, querySet != null)
// more code
$someObject: JSONObject() // to insert
then
addToQuerySet( $user1List, $someObject );
end
Please refer to the documentation here. I have linked to the Drools 7.11.0.Final documentation, specifically the section on functions; the section on declared types is the following section and quite extensive.

How to break the loops in Drools, Mvel dialect?

In the below rule the logic in then-part is getting executed for all Child-objects which pass the given condition, I want to break the loop after the logic
in then-part is executed only once, how to do this
rule "test"
when
Parent( $childList : childList != null, childList.empty == false)
Child('testing'.equalsIgnoreCase(attribute)) from $childList
then
// testLogic
end
If you don't need a reference to the Child object (or any of its attributes) in the RHS, then you can use an exists operator:
rule "test"
when
Parent( $childList : childList != null, childList.empty == false)
exists Child('testing'.equalsIgnoreCase(attribute)) from $childList
then
// testLogic
end
If for some reason you do need the Child object or any of its attributes you can do something like this (although is not very nice):
rule "test"
when
Parent( $childList : childList != null, childList.empty == false)
$c: Child('testing'.equalsIgnoreCase(attribute)) from $childList.get(0)
then
// testLogic
end
Hope it helps,
Reason for infinite loop should be known by identifying whether it is self-loop or complex-loop.
Fact modification within the rule activates the same rule (Self)
Fact modification within the rule activates the different rule (complex) and it activates the original rule.
You can use 'no-loop', next to rule name as no-loop true
You can also restrict by using agenda-group, by checking conditions or like a flag. It depends upon your complexity.

Activate Rules on child objects in Drools

I have two Facts named OptionalCover and OptionalPremiumComponent and OptionalCover has a reference of OptionalPremiumComponent in it. So this is what I'm inserting into working memory.
private static OptionalCover getOptionalCover(Double sumAssured, Double premiumRate) {
OptionalPremiumComponent premiumComponent = new OptionalPremiumComponent();
premiumComponent.setSumAssured(sumAssured);
premiumComponent.setPremiumRate(premiumRate);
OptionalCover optionalCover = new OptionalCover();
optionalCover.setPremiumComponent(premiumComponent);
return optionalCover;
}
kieSession.insert(getOptionalCover(1000000.0, 0.02));
I have created the following rule in drools
import java.lang.Number;
rule "OptionalCoverCalculation"
dialect "java"
when
opc : OptionalPremiumComponent( sumAssured > 1I && sumAssured != null && premiumRate != null && premiumRate > 0.0 )
then
opc.setPremium( opc.getSumAssured() * 0.001 * opc.getPremiumRate() );
System.out.println("Here");
end
The problem is, the above rule is not being fired when I insert the parent object. Do I have to do anything else to enable this behaviour? Is it supported?
Thank you.
The Drools engine has no way of telling that your Cover contains a Component. (Well, it has, as it might use reflection - but where should it stop?)
So, you'll have to insert the OptionalPremiumComponent as well.
To reduce the amount of clutter in your code you might write a sorts of clever methods so that you can insert Cover and Component with a single call.
For instance, if you have many similar "contains" relationships and if you want to reason freely, you might implement s.th. like
interface FactContainer {
List<Object> factChildren(); -- return all contained fact objects
-- OR --
void insertAll( KieSession ks );
}
where factChildren would return a List with the premiumComponent or an empty List, or, alternatively, one method insertAll that handles everything internally.

Exiting after executing a successful rule in Drools 6

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...

Drools ignores the rest of rules if one of rules didn't pass

I'm starting a project with Drools. My project's drl file looks like:
rule "Rule 1"
when
Item( id > 100)
// consequence implementation
rule "Rule 2"
when
Item( type == "phone")
// consequence implementation
rule "Rule 3"
when
Item( screenSize == 5)
// consequence implementation
rule "Rule 4"
when
Item( branch == "motorola")
// consequence implementation
rule "Rule 5"
when
Item( price > 200)
// consequence implementation
When Drools runs I have 5 fired rules. If an item don't pass one of 5 fire rules, I want Drools will ignore the rest 4 fired rules. Does Drools have feature for my purpose?
Another important thing is my Drools runs with AgendaFilter. Does that feature have AgendaFilter support?
First, the phrase in the title "rest of rules" seems to indicate that you think that the rules are evaluated in a certain order (e.g. as written in the file). This is not so: there is no order. Thus, if rules #1 and #2 fired (ìd > 0 and type == "phone") and screenSize != 5, rules #4 and rules #5 will still fire, or they may already have been fired.
To discover whether some fact whose class is referenced in rules 1 to 5 participates in all or some or none of these rules you'll have to create some logic of your own. For instance, declare a type/class with
class Marker {
Set<Integer> rules;
TheClassOfYourItem item;
//...
}
and insert this along with an item. Each rule would then, when firing, add its number to the set.
rule "Rule 1"
when
$item: TheClassOfYourItem(...)
$m: Marker( item == $item )
then
$m.add( 1 );
end
And a rule with low salience can then check whether the set has all/some/none elements.
Edit due to some comment: If no rule should fire unless all rules can fire: simply put all conditions into a single rule and combine all consequences.
rule "Rule 1-5"
when
Item( id > 100, type == "phone". screenSize == 5,
branch == "motorola", price > 200)
then
// ... Do what needs to be done if all five conditions are true
end