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.
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
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.
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've had to create pairs of rules to retract my events. It seems they don't expire. I had wanted one-and-done events. You can see below, they use the default duration, zero.
So for example, if I exclude the retraction rules and then insert the RemoveConnectionEvent first and then insert the CreateConnectionEvent, the RemoveConnection rule will still fire. (Using an agenda listener in my unit tests)
My expectation of an event was that RemoveConnectionEvent would be ignored, it would not do anything if its conditions were not met immediately. I did not expect it to hang around and trigger the RemoveConnection rule once that rules conditions were met when the NewConnection rule responded to the CreateConnectionEvent.
To get my rules to behave as I expected, I created RetractedCreation, RetractedRemoval, and RetractedUpdate. This seems to be a hack. I am imagining a declared my events wrong.
Any ideas?
ps This was a pretty good Q&A but I am not using windows. It might infer that perhaps my hack is an 'explicit expiration policy'.
Test Event expiration in Drools Fusion CEPTest Event Expiration
Here is my rule.
package com.xxx
import com.xxx.ConnectedDevice
import com.xxx.RemoveConnectionEvent
import com.xxx.CreateConnectionEvent
import com.xxx.UpdateConnectionEvent
declare CreateConnectionEvent #role( event ) end
declare UpdateConnectionEvent #role( event ) end
declare RemoveConnectionEvent #role( event ) end
rule NewConnection
when
$connection : CreateConnectionEvent($newChannel : streamId)
not ConnectedDevice( streamId == $newChannel )
then
insert( new ConnectedDevice($newChannel) );
end
rule RetractedCreation
when
$creationEvent : CreateConnectionEvent($newChannel : streamId)
exists ConnectedDevice(streamId == $newChannel)
then
retract($creationEvent)
end
rule RemoveConnection
when
$remove : RemoveConnectionEvent($newChannel : streamId)
$connection : ConnectedDevice( streamId == $newChannel )
then
retract( $connection );
end
rule RetractedRemoval
when
$removalEvent : RemoveConnectionEvent($newChannel : streamId)
not ConnectedDevice(streamId == $newChannel)
then
retract($removalEvent)
end
rule UpdateConnection
when
$connectionUpdate : UpdateConnectionEvent($newChannel : streamId)
$connection : ConnectedDevice( streamId == $newChannel )
then
$connection.setLastMessage();
end
rule RetractedUpdate
when
$removalEvent : UpdateConnectionEvent($newChannel : streamId)
not ConnectedDevice(streamId == $newChannel)
then
retract($removalEvent)
end
This automatic expiry is a rather elusive feature. There's no concise definition when it'll work, and what needs to be done to make it work.
In your apparently simple case where you don't use temporal operators and expect that events are to be retracted after they have matched one rule I'd adopt the following strategy without wasting another thought on "inferred expiration" and "managed lifecycle".
Maybe you have a common (abstract) base class for your events; otherwise create a marker interface and attach it to all events. Let's call this type Event. Then, a single rule
rule "retract event"
salience -999999
when
$e: Event()
then
retract( $e );
end
will take care for all (Create, Update, Remove) events.
Edit You may also use the explicit setting for event expiry.
declare CreateConnectionEvent
#role( event )
#expires(0ms)
end
Make sure to use
KieBaseConfiguration config = ks.newKieBaseConfiguration();
config.setOption( EventProcessingOption.STREAM );
KieBase kieBase = kieContainer.newKieBase( config );
when creating the KieBase. I also recommend to "let the time pass", i.e., advance a pseudo clock or let the thread running a fireUntilHalt for a jiffy or two after fact insertion.
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...