Drools attributes explanations - drools

I have a question about 2 Drools attributes - salience and no-loop
salience:
rule "Hello World"
salience -10
when
$m : Message( status == Message.HELLO, $myMessage : message )
then
System.out.println( $myMessage );
$m.setMessage( "Goodbye cruel world" );
$m.setStatus( Message.GOODBYE );
update( $m );
end
rule "GoodBye"
when
Message( status == Message.GOODBYE, $myMessage : message )
then
System.out.println( $myMessage );
end
We should expect the "GoodBye" rule to be fired first(since its salience is higher) however that doesn't happen and instead the "Hello World" rule gets fired up first and only then "GoodBye"
no-loop:
I understand that this attribute prevent from a rule to be executed to the same fact which will cause an infinite loop. My question is about an example about this attribute that I don't quite understand:
rule "interest calculation"
no-loop
when
$account : Account( )
then
modify($account) {
setBalance((long)($account.getBalance() * 1.03));
}
end
If there wasn't "no-loop" why that will cause an infinite loop?

Re salience: Logic always beats salience. If Message.status is initially set to Message.HELLO, the other rule doesn't qualify and "Hello World" is executed.
Re no-loop: A modify/update simply means that re-evaluation of everything begins from scratch as far as the modified fact is concerned. So, Account is updated, goes back to square one, and reevaluation creates another activation of this trivially matching rule.

Related

Drools: Time continuous drools rules not working

I'm trying to test the following drools rules:
declare Message
#role(event)
#timestamp(timestamp)
#expires(10s)
end
rule "throttle state activated"
when
$meta: CurrentState(throttleState == false)
$lastMessage: Message($lastTimestamp: timestamp)
not (Message(timestamp > $lastMessage.timestamp))
$messages: ArrayList(size > 20) from collect(
$message: Message() over window:time(10s)
)
then
$meta.setLastThrottled($lastMessage.getTimestamp());
$meta.setThrottleState(true);
update($meta);
end
rule "throttle state deactivated"
when
$meta: CurrentState(throttleState == true)
$lastMessage: Message($lastTimestamp: timestamp)
not (Message(timestamp > $lastMessage.timestamp))
$messages: ArrayList(size <= 20) from collect(
$message: Message() over window:time(10s)
)
then
$meta.setThrottleState(false);
update($meta);
end
With the following two tests, these are in Scala and are using must matchers:
"rules" must {
"successfully trigger (activate)" in {
reset() // Resets the kie session and event listener
// The insertAndFire function first advances the pseudo clock, then inserts the message, then triggers all
// drools rules
(1 to 50).foreach(i => insertAndFire(mockMessage(offsetMSec = 200 * i), timeAdvanceMSec = 200))
// The sListener simply counts every drools rule fire amount
sListener.getCount("throttle state activated") mustBe 1 // THIS TEST SUCCEEDS
}
"successfully trigger (deactivate)" in {
reset()
(1 to 50).foreach(i => insertAndFire(mockMessage(offsetMSec = 200 * i), timeAdvanceMSec = 200))
(1 to 60).foreach(i => {
// Now for the next 60 seconds, we advance the clock and fire all rules to force the deactivation to not have 20 messages in the last 10 seconds.
// This should theoretically already trigger the rule after 10 forloop iterations.
clock.advanceTime(1, TimeUnit.SECONDS)
kieSession.fireAllRules
})
sListener.getCount("throttle state activated") mustBe 1 // THIS TEST SUCCEEDS
sListener.getCount("throttle state deactivated") mustBe 1 // !!! THIS TEST FAILS !!!
}
}
I've tried everything, and have already debugged that the throttleState variable in the CurrentState actually turns to true, but for some reason it does not want to actually trigger the deactivation rule.
When using these rules in live code, not testing code, it works fine, so I don't understand why the rules do not properly trigger. I'm using a PseudoSessionClock to simulate time during these tests and this works fine for every single other rule. My suspicion is that it has to do with the collect in the deactivation rule, but I cannot pinpoint it.
I've also tried forcefully waiting 10 seconds using a timer in real life, which of course hogs the test, but this also fails. It would not be logical that this solution would work as the activation rule does properly trigger, but it was worth a try.
MCVE Project: https://github.com/ThijmenL98/DroolsMCVE

How to pass and read the vlaues from map in Drools DRT template?

I am trying to pass a map in DRT template of the of the drools like this
TEMPLATE
template "another map"
rule "test map_2"
no-loop true
dialect "java"
when
namedmap:Map(map:"${namedTags}")
review:FactNew(
(compareNamedTags(namedmap,namedTags))
)
then
System.out.println("Value is present in the list: "+ namedmap.get("objective"));
modify(review){setComment("accepted")}
end
end template
DRL GENERATED
rule "test map_2"
no-loop true
dialect "java"
when
namedmap:Map(map:"{key1=value1, key2=value2}")
review:FactNew(
(compareNamedTags(namedmap,namedTags))
)
then
System.out.println("Value is present in the list: "+ namedmap.get("key2"));
modify(review){setComment("accepted")}
end
FUNCTION TO CHECK THE MAP
function Boolean compareNamedTags(Map namedTagsMap, Map namedTags){
System.out.println("Comparing the named Tags map");
System.out.println("namedTag -->"+namedTagsMap );
System.out.println("namedTagsMap -->"+namedTags );
System.out.println("equality -->"+namedTagsMap.keySet().equals(namedTags.keySet()) );
boolean flag = false;
flag = namedTagsMap.equals(namedTags);
return true;
}
While firing the rule there is no error but no output is printed. Also there is no call to the function why? Is this the right to pass the map in DRT and use it in the when condition of the rule.
Please provide some guidance on how one can pass a map in the DRT template and use it.

Problem with pattern matching in Drools 7.57.0.Final

I'm trying to run this GitHub project using Drools 7.57.0.Final instead of 7.39.0.Final which was used in original project. And I found some issues. The issue that most triggers me is the one in the Section 6, Step 5. The problem lies in the Drools file VisaApplicationValidationWithAgendaAndSalience.drl. Here is the content with the "debug" statement that I have added:
package io.github.aasaru.drools.section06.step5
import io.github.aasaru.drools.domain.Passport;
import io.github.aasaru.drools.domain.Validation;
import io.github.aasaru.drools.domain.VisaApplication;
import java.time.LocalDate;
rule "Invalidate visa application with invalid passport"
dialect "mvel"
agenda-group "validate-application"
salience 20
when
$passport: Passport( validation == Validation.FAILED )
$visaApplication: VisaApplication( passportNumber == $passport.passportNumber,
validation != Validation.FAILED )
then
System.out.println( "Set " + $visaApplication + " invalid as " + $passport + " hasn't passed validation");
modify($visaApplication) {
setValidation( Validation.FAILED )
}
end
rule "Set application without validation info as passed"
dialect "mvel"
agenda-group "validate-application"
salience 10
when
$visaApplication: VisaApplication( validation == Validation.UNKNOWN )
then
System.out.println("debug >>>>>>>>>>>>> " + $visaApplication.validation);
System.out.println( $visaApplication + " is without validation info, consider OK for now" );
modify($visaApplication) {
setValidation( Validation.PASSED )
}
end
rule "Invalidate visa application where passport expires earlier than 6 months after visit end date"
dialect "mvel"
agenda-group "validate-application"
salience 20
when
$passport: Passport( validation != Validation.FAILED )
$visaApplication: VisaApplication( passportNumber == $passport.passportNumber,
$passport.expiresOn.isBefore(visitEndDate.plusMonths(6)),
validation != Validation.FAILED )
then
System.out.println( "Set " + $visaApplication + " invalid as " + $passport + " not valid 6 months after visit");
modify($visaApplication) {
setValidation( Validation.FAILED )
}
end
and here is the part of the console output that regards to the previous rules:
Set VisaApplication(#1, pass:CA-SARAH-1) invalid as Passport[no:CA-SARAH-1, name:Sarah Murphy] hasn't passed validation
Set VisaApplication(#4, pass:AU-JAMES-4) invalid as Passport[no:AU-JAMES-4, name:James Brown] not valid 6 months after visit
debug >>>>>>>>>>>>> FAILED
VisaApplication(#1, pass:CA-SARAH-1) is without validation info, consider OK for now
Set VisaApplication(#1, pass:CA-SARAH-1) invalid as Passport[no:CA-SARAH-1, name:Sarah Murphy] hasn't passed validation
debug >>>>>>>>>>>>> UNKNOWN
VisaApplication(#2, pass:CA-SIMON-2) is without validation info, consider OK for now
debug >>>>>>>>>>>>> UNKNOWN
VisaApplication(#3, pass:AU-EMILY-3) is without validation info, consider OK for now
As is obvious, the middle rule, the one with the salience 10, has been fired not only for the pattern validation == Validation.UNKNOWN but also for the pattern validation == Validation.FAILED! That is wrong and it happens in Drools 7.57.0.Final and doesn't happen in Drools 7.39.0.Final used by the original project. Is it a Drools bug or what? I'm pretty new to Drools so this really confuses me.
Second, why do I get the console output Set VisaApplication(#1, pass:CA-SARAH-1) ... twice? It seems to be a consequence of the reevaluation of the 1st rule, but this shouldn't have happened as this is the stateful and not the stateless session.
If you want to reproduce the console log, download the project, change Drools version to the latest one 7.57.0.Final, run the app io.github.aasaru.drools.section06.VisaIssue, and when asked,
enter 5 for the step and no when asked whether all passports should be considered as expired.
Congratulation, you found drools bug DROOLS-6542 - fixed in 7.60.0.Final
There is a workaround - remove mvel dialect for the rule "Invalidate visa application with invalid passport".
BTW, I'd like to propose you drools testing library which may save you great amount of time to mind complicated rules and simplify writing test scenarios. Here is how test may look like.
#DroolsSession(
resources = "classpath*:**/section06/step5/*",
showStateTransitionPopup = true)
public class Section6Step5Test {
#RegisterExtension
public DroolsAssert drools = new DroolsAssert();
#Test
public void test() {
ApplicationRepository.getPassports().forEach(drools::insert);
ApplicationRepository.getVisaApplications().forEach(drools::insert);
drools.getSession().getAgenda().getAgendaGroup("issue-visa").setFocus();
drools.getSession().getAgenda().getAgendaGroup("validate-application").setFocus();
drools.getSession().getAgenda().getAgendaGroup("validate-passport").setFocus();
drools.fireAllRules();
assertEquals(1, drools.getObjects(Visa.class).size());
}
}
Here is visualization popup of the test on 7.39.0.Final (or 7.57.0.Final without mvel dialect).
and the same on 7.57.0.Final (with mvel dialect)

How to insert or update status when "when" condition failed in drools?

In drools we have when and then. If a condition is satisfied, then we go to the then condition and update a status, like obj.setStatus("transaction success", "review required").
But when the when condition is not satisfied, then I want to update obj.setStatus("transaction Failed", "review not required").
How is this possible in Drools?
The best way would be to initialize obj.status to "failed". After all rules have fired, you'll either still have this or the updated status showing "success".
If you need a rule firing, you can write a rule catching the unmodified fact:
rule "catch failures"
salience -999999
when
$obj: MyFact( status == null ) // no initial value
then
modify( $obj ){ setStatus("transaction Failed", "review not required") }
end
There is not a single rule. If we do like that then our rules size will be increase.
//FailureMessage :- Equipment breakdown coverage size is greater than 0
rule "CPP_ReviewLossControl"
ruleflow-group "CPPReview"
lock-on-active
when
$commPolicy : CommlPolicyInfoBean();
$STPRulesResponseBean:STPRulesResponseBean();
$lobInfo: LOBInfoBean("CPP".equalsIgnoreCase(lob) && losses.size() >= 3) from $commPolicy.getLobs();
then
System.out.println("Creating Loss history task for AMR");
$STPRulesResponseBean.getResultMap().put("CPP_ORD_LOSS_CNTRL",true);
end
//FailureMessage :-Business category should be General Framing, Window or Window Framing, Stucco and/or EIFS application including masonry/drywall contractors., Roofing, Exterior Siding/Water Proofing/Caulking, Any other exterior finishing work.
rule "CPP_ReviewBusinessCategory"
ruleflow-group "CPPReview"
lock-on-active
when
$STPRulesResponseBean:STPRulesResponseBean();
$commlPolicyInfoBean : CommlPolicyInfoBean();
$lobInfo: LOBInfoBean("CPP".equalsIgnoreCase(lob)) from $commlPolicyInfoBean.getLobs();
AccountInfoBean(businessCategoryCode =="23" && (primaryActivities contains ("23813") || primaryActivities contains ("23831") || primaryActivities contains "23816")) from $commlPolicyInfoBean.getAccountInfo()
then
System.out.println("Creating Business category");
$STPRulesResponseBean.getResultMap().put("CPP_REV_SPL_RISK",true);
end
and there is setFocus also.

Drools rules categorization

I am looking for a way to categorize rules in my drl files and determine which category/ categories of rules fired.
I see that the Drools Guvnor documentation has something about categorization http://docs.jboss.org/drools/release/5.2.0.Final/drools-guvnor-docs/html/ch03.html#d0e228. However, it is unclear to me if this is metadata maintained by Guvnor or if it is part of the drl file/ rules execution. I have not been able to find a sample DRL file with category mentioned in it. And, a way to determine the category/ categories of rules that got fired.
I am aware of activation-group but that doesn't quite fit my need since once a rule in the activation-group fires the other rules in the group are not evaluated.
Thanks
The best way to classify rules is to do it via metadata. A metadata entry is added to the rule:
rule metademo
#meta2( 123 )
#meta3( foo )
#meta4( "foo" )
when...then...end
You access the metadata via a Rule, obtained from a package or some event:
for( Rule rule: kPackage.getRules() ){
Map<String,Object> key2meta = rule.getMetaData();
for( Map.Entry<String,Object> entry: key2meta.entrySet() ){
System.out.print( " #" + entry.getKey() + "( " );
Object value = entry.getValue();
System.out.print( "[" + value.getClass().getSimpleName() + "] "
+ value.toString() );
System.out.println( " ) " );
}
}
Output:
#meta2( [Integer] 123 )
#meta4( [String] foo )
#meta3( [String] foo )
There was a plan to permit Maps as value so you might use
#meta7( foo = "foo", bar = "bar" )
This didn't work in 5.5, and I never tried it again.