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.
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 am using Drools version 6.5.0.Final and I have a simple rule like:
rule "Test Rule"
when
$obj : MyObject(testValue == null || testValue != "NEW")
then
$obj.testValue = "NEW";
update($obj);
end
So I also implemented a RuleRuntimeEventListener class and it gets invoked when this rule is executed. The problem is the passed in ObjectUpdatedEvent instance's getObject() and getOldObject() calls return the same exact object - which is the object after the update. So for this the getOldObject() call returns an object that has a value of "NEW" for the testValue property. I am expecting getOldObject to return me the value of testValue that isn't "NEW" which is after it was changed by the rule.
This seems like a bug to me and was wondering if anybody had any similar issues with this or if there is any workaround? I want to be notified when any property of an object is changed by a rule and what the old and new values were.
I see that there is support for using the java bean's PropertyChangeListener, but that looks like I have to create the fact objects with a addPropertyChangeListener and removePropertyChangeListener methods and also in all my fact's setter methods I have to use PropertyChangeSupport and fire a property change event via firePropertyChange method. I really don't want to have to do that.
Thanks
This seems to be a bug in Drools, the issue is also present in 7.16.0.FINAL. By doing the following
System.out.println(getOldObject() == getObject());
You will get an output of "true" indicating that both objects are of the same reference.
I just raised it as a bug on the Drools site
https://issues.jboss.org/browse/DROOLS-4305
public class Differ implements AgendaEventListener {
private List<Object> oldFacts;
public void beforeMatchFired(BeforeMatchFiredEvent event){
List<Object> facts = event.getMatch().getObjects();
// copy (!) facts into oldFacts
}
public void afterMatchFired(AfterMatchFiredEvent event){
List<Object> facts = event.getMatch().getObjects();
// compare facts with oldFacts
}
//...
}
You may want to be more selective in copying and comparing. Also, a generic way of comparing using the stringified object contents might be considered.
BeforeMatchFiredEvent
I'm not sure why this is causing me an issue, but I'm using Orient 2.1.19, found this in 2.1.12 as well. We are building some hooks to implement a method of encryption. I know 2.2 implements some encryption, but we had some further requirements.
Anyway, we have hooks for onRecordAfterRead, onRecordBeforeCreate and onRecordBeforeUpdate. It works for most statements fine, but with the hook in place, running a query that sets a link property using a subquery in an insert fails. Here's an example query:
create EDGE eThisEdge from (select from vVertex where thisproperty = 'this') to (select from vVertex where thatProperty = 'that' ) set current = (select from lookupCurrent where displayCurrentPast = 'Current');
Runnning this query gives me the error:
com.orientechnologies.orient.core.exception.OValidationException: The field 'eThisEdge.current' has been declared as LINK but the value is not a record or a record-id.
It's some issue with the way a subquery is ran during just an insert though, because if I run the insert without setting any properties, then run an update to set the properties, that works. I'd hate to have to rewrite all of our inserts for our base data and our coding just as a work around for this, and it seems like I'm just missing something here.
Has anyone seen this kind of issue with hooks as well?
The biggest issue seems to be surrounding the onRecordBeforeCreate code. We are trying to have a generic hook that encrypts strings in our database. Here's the basics of the onRecordBeforeCreate method:
public RESULT onRecordBeforeCreate( ODocument oDocument) {
RESULT changed = RESULT.RECORD_NOT_CHANGED;
try {
if(classIsCipherable(oDocument)) {
for (String field : oDocument.fieldNames()) {
if (oDocument.fieldType(field) != null && oDocument.fieldType(field) == OType.STRING && oDocument.field(field) != null) {
oDocument.field(field, crypto.encrypt(oDocument.field(field).toString()));
changed = RESULT.RECORD_CHANGED;
}
}
}
return changed;
} catch (Exception e) {
throw new RuntimeException( e );
}
Is there anything there that looks obvious that I'd have issues with running a create edge statement that sets properties with a property that is a link?
The query select from lookupCurrent where displayCurrentPast = "Current" return more than one element, you must use a LinkList or a LinkSet
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...
As stated in title I'd like to change conflict resolver in my Drools project. I found following snippet on this site
ConflictResolver[] conflictResolvers =
new ConflictResolver[] { SalienceConflictResolver.getInstance( ),
FifoConflictResolver.getInstance( ) };
RuleBase ruleBase = java.io.RuleBaseLoader( url, CompositeConflitResolver( conflictResolver));
However it lacks of information where to put it and what sholud be url parameter.
Thank you in advance for any help.
As the documentation says:
Drools 4.0 supported custom conflict resolution strategies; while this
capability still exists in Drools it has not yet been exposed to the
end user via knowledge-api in Drools 5.0.
http://docs.jboss.org/drools/release/5.2.0.Final/drools-expert-docs/html/ch04.html
So if you are using Drools 5+ you will not be able to change conflict resolver unless you do some reflection magic. The Conflict Resolver is settled inside the Agenda object of the StatefulKnowledgeSession object. You can see this by using debugger (it's the content of Agenda object):
To replace ConflictResolver, first you need instance of StatefulKnowledgeSession (which will be ksession in the following snippet). Then you need to extract some nested private fields and after that you can replace field value with instance of i.e RandomConflictResolver. Full code:
Agenda agenda = ksession.getAgenda();
Field agendaField = agenda.getClass().getDeclaredField("agenda");
agendaField.setAccessible(true);
Object agendaObject = agendaField.get(agenda);
Field mainField = agendaObject.getClass().getDeclaredField("main");
mainField.setAccessible(true);
Object mainObject = mainField.get(agendaObject);
Field queueField = mainObject.getClass().getDeclaredField("queue");
queueField.setAccessible(true);
Object queueObject = queueField.get(mainObject);
Field comparatorField = queueObject.getClass().getDeclaredField("comparator");
comparatorField.setAccessible(true);
Object comparator = comparatorField.get(queueObject);
ConflictResolver randomResolver = org.drools.conflict.RandomConflictResolver.getInstance();
comparatorField.set(queueObject, randomResolver);
Based on : documentation and debugger session.
To demonstrate that the order of firings does not affect the overall outcome I'd use an AgendaFilter. I'm sketching the outline using 6.x, but this API hasn't changed since 5.x.
KieBase kieBase = ...;
Collection<KiePackage> packages = kieBase.getKiePackages();
List<Rule> rules = new ArrayList<>();
for( KiePackage p: packages ){
rules.addAll( p.getRules() );
}
Now you have all the rules. Build this simple filter that accepts a single rule:
class Selector implements AgendaFilter {
List<Rule> rules;
int ruleIndex;
Selector( List<Rule> rules ){
this.rules = rules;
}
void setRuleIndex( int value ){ this.ruleIndex = value; }
int getRulesSize(){ return rules.size(); }
boolean accept(Match match){
return match.getRule().equals( rules.get( ruleIndex ) );
}
}
Instantiate:
Selector selector = newSelector( rules );
You can now execute all rules that are activated (but see below):
for( int i = 0; i < selector.getRulesSize(); ++i ){
int fired = kieSession.fireAllRules( selector, i );
}
Any other permutation of 0..size-1 may produce another sequence of firings. You may do this systematically for a small number of rules, or you may use some random permutations.
A more efficient test would keep track of the Match data passed to the filter in the first run and use only these for successive executions.
Caution The approach outlined so far does not consider changes in Working Memory. It would be possibe for rule n to become activated when some rule n+k is fired. If you do have changes of Working Memory, you will have to
do {
int sumf = 0;
for( int i : somePermutation ){
int fired = kieSession.fireAllRules( selector, i );
sumf += fired;
}
} while( sumf > 0 );
I have never done a test like this. It seems that getting the correct result by depending on the innate order of rule firings is extremely rare, in contrast to getting all sorts of wrong results from this order.
Note Other permutations of the rule firing order are possible by changing the order of rules in the DRL (or in their packages?) or by changing the order of fact insertions into working memory. For certain scenarios, even this may provide enough test cases for showing what you intend.