Drools update() not causing refire - drools

I have the following 2 rules in my system. I am using 7.73.0. It is being run in a stateful session.
Rule 11 will fire but Rule 10 is not fired as a result of Rule 11. I would expect rule 11 to fire and then rule 10 because of the update statement.
I have debugged my code with a breakpoint on the setValue("A") in rule 11 and see that the method is being called and the value is set properly.
Can anyone tell me why rule 10 will not fire as a result of the update statement?
rule "MYRULES_10"
when
prod : Product(sku != "1")
$txt1 : TextOption(sku == "mm", value == "A")
then
prod.setOptionEnabled( "to2", false, "Not available rule 10" );
end
// rule values at B11, header at B5
rule "MYRULES_11"
when
prod : Product(sku != "1")
$opt1 : TextChoiceOption(sku == "mt")
exists (OptionValue(optionValueValue in ("val1")) from $opt1.value)
$txt1 : TextOption(sku == "mm")
then
$txt1.setValue("A"); update($txt1);
end
additional info:
If I fireAllRules on the session immediately after the initial fireAllRules, Rule 10 still will not fire.
If I take the result of the first stateful session and put all the facts into a second session and fireAllRules then rule 10 is fired and then rule 11 is fired.
If I put the fact in initially with fact mm having value A then rule 10 fires first and then rule 11 will fire.

Are you using a Stateless Session or Sequential Mode for your session? If yes, then this is the expected behaviour. https://docs.drools.org/8.31.0.Final/drools-docs/docs-website/drools/rule-engine/index.html#phreak-sequential-mode-con_rule-engine
Another option is that property reactivity is now working as expected because you are using the discouraged update() function to let Drools know about the modification of your facts. Try to use modify() instead:
...
then
modify($txt1){
setValue("A")
}
end

Related

Drools - Sliding window not working as expected

I'm new to drools. I've defined the following rule to add the last two numbers in the stream together. I then send in a stream of 100 events, with the values set from 1 to 100. So I would expect the output to be 0, 1, 3, 5, 7, 9, 11, 13 etc.
declare TestEvent
#role( event )
value : int
end
rule "Simple Rule"
when
$sum : Integer() from accumulate ( TestEvent( $value : value ) over window:length( 2 ); sum( $value) )
then
System.out.println("Value: " + $sum );
end
The session is started using "fireUntilHalt" in a separate thread. If I put a delay of 10 milliseconds between every event inserted it works as expected. However when I don't have the delay, the results aren't as expected. Usually it just prints 0 and 197. Sometimes I might get a number or two in-between as well.
Why does this happen and what I should do to fix?
Ok, I finally understand it.
Having fireUntilHalt running in a separate thread means that the rules are only evaluated every now and then (not sure what that time period is). My assumption was that they would be evaluated on every insert. So on every insert the accumulator values are updated, but the rules evaluated aren't evaluated. So because my rules are inserted quickly (in about one second), the first and last insert is all that seems to be evaluated.
To get this to work so every insert is evaluated, I removed the fireUntilHalt, and did a separate fireAllRules after each insert.
Sliding windows are a crutch. Replace it by simple logic.
class Pred {
Integer pred;
}
rule procInt1
when
$p: Pred( pred == null )
$i: Integer()
then
modify( $p ){ setPred( $i ); }
end
rule procInt
when
$p: Pred( $i1: pred != null )
$i2: Integer()
then
System.out.println( $i1 + $i2 );
modify( $p ){ setPred( $i2 ); }
end
The fact that you have two threads doesn't mean that you can't have race conditions. The thread inserting events isn't suspended and so everything is possible.
Time related rules rely on 'time'. Smallest time tick for drools seems to be 1ms. Rules will be evaluated on each insertion (and respective consequences will be added to the agenda) but agenda will be executed on next millisecond tick (or when you explicitly fire all rules).

Drools : Rule firing multiple times

I'm new to Drools and have hit a problem.
I've simplified the rule to exhibit the problem:
rule "test"
when
$ev : TestEvent()
$evList : ArrayList( size >= 3 ) from collect
(
TestEvent(linkId == $ev.getLinkId())
)
then
System.out.println("Rule fired!")
end
Basically, I want to count Events occurring on a particular Link (a Link is a section of road). When 3 events occur on the same Link I want the rule to fire.
The rule above is almost working, but when it fires, it fires 3 times, once for each event. I only want it to fire once.
What am I missing?
Many thanks in advance.
The first pattern picks any TestEvent irrespective of its linkId. If there are n TestEvent facts with a certain linkId, the acivation proceeds n times.
To restrict this rule to fire once you could select a single TestEvent out of any such group of n. Any attribute with a unique ordered value may be used, and if you have events also the event timestamp is available.
rule "test"
when
$ev: TestEvent( $lid: linkId )
not TestEvent( linkId == $lid, this before $ev )
$evList : ArrayList( size >= 3 ) from collect
(
TestEvent(linkId == $lid)
)
then
System.out.println("Rule fired!")
end
I got this working by changing my approach to the problem. I've created Link objects now and then tie the events back to the Link.
The rule ends up
rule "test"
when
$link : Link()
$events : ArrayList( size >= 3 ) from collect (TestEvent(link == $link))
then
System.out.println("Rule fired!")
end
This only fires once per link which is what I need.

Drools accumulate method that returns zero if no matching source fact

I am trying to implement a rule in Drools that calculates the sum of a some property of a fact. That works great using accumulate and sum. The problem is that when there are not fact that matches the criteria in the source part of the accumulate method the rule is not executed.
I would like the sum method to return zero if no fact is matching and that the rest of the when clauses is checked. Is that possible somehow?
Update:
I am using Drools 6.0.1
The problem seems to lie in the the and clause. Here is a code that is my problem.
rule "accu"
when
$n: Number()
from accumulate( $o: Order() and OrderLine( $v: quantity ),
sum($v))
then
System.out.println("*#*#*#*#*#*#*#*#*#* Accu has fired *#*#*#*#*#*#*#*#*#");
end
With only Order or OrderLine it works. I have a feeling I am attacking the problem the wrong way. In my real case the value I want to sum up is in the OrderLine but the criteria is in another class.
$ol : OrderLine($q : quantity)
and
$ac : ArticleClass(orderLine == $ol, crtiteria1=efg, criteria2=abc)
But accumulate does return 0 when there are no matching elements.
rule accu
when
$n: Number()
from accumulate( Fact( prop == "C", $v: value ),
sum($v))
then
//...
end
This fires in the absence of Fact facts with prop == "C" and it fires if there are no Fact facts at all. (Drools 5.5.0)
Please provide full code reproducing the error, Drools version, etc.

drools: rules executed more than one time

I am trying hands on at the Drools rule engine , I am quite a beginner.
I have the following rules in place in a single rule file:
rule "A stand alone rule"
salience 2
no-loop
when
$account : Account()
Account($account.balance>100)
then
System.out.println("balance>100");
System.out.println($account.getBalance());
System.out.println($account.getCustomer().getName());
end
rule "A second Rule"
salience 1
no-loop
when
$account : Account()
Account($account.balance<100)
then
System.out.println("balance<100");
System.out.println($account.getBalance());
System.out.println($account.getCustomer().getName());
end
In the StatefulKnowledgeSession I am passing TWO accounts , one with balance 15000 another with balance 15 ,
Account account=new Account(7l,15000l);
Account account1=new Account(5l,15l);
Customer customer = new Customer("Samrat", 28, "Sector51", account);
Customer customer1 = new Customer("Alexi", 28, "Sector50", account1);
account.setCustomer(customer);
account1.setCustomer(customer1);
session.insert(account);
session.insert(account1);
session.fireAllRules();
According to me the expected result should be that each rule should be fired only once and the corresponding object should be printed.
But the result I am getting is :
balance>100
15000
Samrat
balance>100
15000
Samrat
balance<100
15
Alexi
balance<100
15
Alexi
I am not able to understand why each rule is running twice ????
Using multiple patterns (and not specifying any relation between them) will create a full Cartesian product (just like a select on multiple tables without a join clause).
So, the rule:
rule A
when
Account()
Account()
then
...
end
will be activated N^2 times for N objects of type Account.
One solution could be to use the magic field 'this' to specify that the second account is the same as the first one:
rule A
when
$a: Account()
Account(this == $a)
then
...
end
But, going back to your example, I think you don't even need to use 2 different patterns. You could rewrite your rules as following:
rule "A stand alone rule"
salience 2
no-loop
when
$account: Account(balance>100)
then
System.out.println("balance>100");
System.out.println($account.getBalance());
System.out.println($account.getCustomer().getName());
end
rule "A second Rule"
salience 1
no-loop
when
$account: Account(balance<100)
then
System.out.println("balance<100");
System.out.println($account.getBalance());
System.out.println($account.getCustomer().getName());
end
Hope it helps,
I was comparing two objects of same class and was wondering why the rules are getting fired multiple times. However after reading explanation from Esteban Aliverti I thought that my rule might also be creating Cartesian product.
So I replaced "and" from the rules to "," and it worked perfectly. However, I could not understand why "and" was creating Cartesian product.
Earlier my rule was --
rule "Rule 1"
when
$first : RuleC() and
second : RuleC(this != $first) and
RuleC($first.outcome < outcome) and
RuleC($first.target == target)
then
System.out.println("The rule has been fired ");
end
Later my rule became (And it is working absolutely fine) --
rule "Rule 1"
when
$first : RuleC() and
second : RuleC(this != $first, $first.outcome < outcome, $first.target == target)
then
System.out.println("The rule has been fired ");
end

Issues using retract in then condition of a rule

I am trying to write a rule to detect if a given event has occurred for 'n' number times in last 'm' duration of time.
I am using drools version 5.4.Final. I have also tried 5.5.Final with no effect.
I have found that there are a couple of Conditional Elements, as Drools call it, accumulate and collect. I have used collect in my sample rule below
rule "check-login-attack-rule-1"
dialect "java"
when
$logMessage: LogMessage()
$logMessages : ArrayList ( size >= 3 )
from collect(LogMessage(getAction().equals(Action.Login)
&& isProcessed() == false)
over window:time(10s))
then
LogManager.debug(Poc.class, "!!!!! Login Attack detected. Generating alert.!!!"+$logMessages.size());
LogManager.debug(Poc.class, "Current Log Message: "+$logMessage.getEventName()+":"+(new Date($logMessage.getTime())));
int size = $logMessages.size();
for(int i = 0 ; i < size; i++) {
Object msgObj = $logMessages.get(i);
LogMessage msg = (LogMessage) msgObj;
LogManager.debug(Poc.class, "LogMessage: "+msg.getEventName()+":"+(new Date(msg.getTime())));
msg.setProcessed(true);
update(msgObj); // Does not work. Rule execution does not proceed beyond this point.
// retract(msgObj) // Does not work. Rule execution does not proceed beyond this point.
}
// Completed processing the logs over a given window. Now removing the processed logs.
//retract($logMessages) // Does not work. Rule execution does not proceed beyond this point.
end
The code to inject logs is as below. The code injects logs at every 3 secs and fires rules.
final StatefulKnowledgeSession kSession = kBase.newStatefulKnowledgeSession();
long msgId = 0;
while(true) {
// Generate Log messages every 3 Secs.
// Every alternate log message will satisfy a rule condition
LogMessage log = null;
log = new LogMessage();
log.setEventName("msg:"+msgId);
log.setAction(LogMessage.Action.Login);
LogManager.debug(Poc.class, "PUSHING LOG: "+log.getEventName()+":"+log.getTime());
kSession.insert(log);
kSession.fireAllRules();
LogManager.debug(Poc.class, "PUSHED LOG: "+log.getEventName()+":"+(new Date(log.getTime())));
// Sleep for 3 secs
try {
sleep(3*1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
msgId++;
}
With this, what I could achieve is checking for existence of the above said LogMessage in last 10 secs. I could also find out the exact set of LogMessages which occurred in last 10 secs triggering the rule.
The problem is, once these messages are processed, they should not take part in next cycle of evaluation. This is something which I've not be able to achieve. I'll explain this with example.
Consider a timeline below, The timeline shows insertion of log messages and the state of alert generation which should happen.
Expected Result
Secs -- Log -- Alert
0 -- LogMessage1 -- No Alert
3 -- LogMessage2 -- No Alert
6 -- LogMessage3 -- Alert1 (LogMessage1, LogMessage2, LogMessage3)
9 -- LogMessage4 -- No Alert
12 -- LogMessage5 -- No Alert
15 -- LogMessage6 -- Alert2 (LogMessage4, LogMessage5, LogMessage6)
But whats happening with current code is
Actual Result
Secs -- Log -- Alert
0 -- LogMessage1 -- No Alert
3 -- LogMessage2 -- No Alert
6 -- LogMessage3 -- Alert1 (LogMessage1, LogMessage2, LogMessage3)
9 -- LogMessage4 -- Alert2 (LogMessage2, LogMessage3, LogMessage4)
12 -- LogMessage5 -- Alert3 (LogMessage3, LogMessage4, LogMessage5)
15 -- LogMessage6 -- Alert4 (LogMessage4, LogMessage5, LogMessage6)
Essentially, I am not able to discard the messages which are already processed and have taken part in an alert generation. I tried to use retract to remove the processed facts from its working memory. But when I added retract in the then part of the rule, the rules stopped firing at all. I have not been able to figure out why the rules stop firing after adding the retract.
Kindly let me know where am I going wrong.
You seem to be forgetting to set as processed the other 3 facts in the list. You would need a helper class as a global to do so because it should be done in a for loop. Otherwise, these groups of messages can trigger the rule as well:
1 no triggering
1,2 no triggerning
1,2,3 triggers
2,3,4 triggers because a new fact is added and 2 and 3 were in the list
3,4,5 triggers because a new fact is added and 3 and 4 were in the list
and so on
hope this helps