Having trouble with rule that checks two instances of same class - drools

I have a rule that says
rule "bcs-set"
when
Param( Feature == "BCS", Name == "primary" )
Param( Feature == "BCS", Name == "seconday" )
then
insert ("addition")
end
I have created two Param objects but it seems like drools can't find both of the Param objects.
If I take out the first Param check it works but not with both of the Param checks in the rule.
The Param class is as follows:
public class Param {
private String feature;
private String name;
public String getFeature(){
return feature;
}
public void setFeature(String feature){
this.feature = feature;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
Anyone has any ideas?

It's very likely that you have done something like
Param p = new Param();
p.setFeature( "BCS" );
p.setName( "primary" );
kSession.insert( p );
p.setName( "secondary" );
kSession.insert( p );
kSession.fireAllRules();
Note that insert doesn't copy; it just uses the reference. - This is how it should be done:
Param p1 = new Param();
p1.setFeature( "BCS" );
p1.setName( "primary" );
kSession.insert( p1 );
Param p2 = new Param();
p2.setFeature( "BCS" );
p2.setName( "secondary" );
kSession.insert( p2 );
kSession.fireAllRules();
It is, of course, possible that you have done something else, but this fits the facts as you have related them. Sadly, you have omitted this highly important part of the picture.

Related

Setting and getting a Global variable in Drools

I have something like this in drl file:
import java.lang.String
global String result;
rule ''Rule 1'' when some condition
then
result = "PASS";
kcontext.getKnowledgeRuntime().setGlobal("Result", result); // I got an "Unexpected global" exception.
System.out.println("result = "+ Result);
Also, I don't know how to access this global variable from my MyService.java class.
I was trying to set a global variable from the drl file not my java class like Service class.
All I had to do was the following and it worked successfully
import java.lang.String
global String result;
rule ''Rule 1''
when
some condition
then
String grade = "PASS";
kcontext.getKnowledgeRuntime().setGlobal("result", grade);
end
Also, the global variable name should match what I pass on the setGlobal("result",...).
And then get the global variable using the session I have in the Service class. like:
session.getGlobal("result");
Your rule should not be touching the 'kcontext'. What in the world are you trying to do? result = "PASS" is sufficient for setting the value of the global.
global String result
rule "Rule 1"
when
// some condition
then
result = "PASS";
end
Of course it's not going to work like you want it to because you need to change the value of the existing object; you can't overwrite it like that. Some options might be a "ResultsHolder" sort of class with a boolean variable you can set; or maybe even an AtomicBoolean that you can call set on.
To fire rules with a global, you need to add the global objects to the KieBase before invoking your rules:
var value = ...; // some OBJECT which you are going to pass in as a global
KieSession session = ruleBase.newStatefulSession();
session.insert(...); // insert data
session.setGlobal( "myGlobalFoo", value ); // sets the global; note the name must match the rule file!
session.fireAllRules();
After the rules are fired, you'll have your reference to value that you can use. This is also why you can't pass strings as globals and expect them to capture changes -- Java is pass-by-value, not pass-by-reference.
Here's an example for passing results out of the rules. This toy app will check the student's score on a test and then decide if they passed or failed.
Classes:
class Student {
private String name;
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
}
class Exam {
private String name;
private Double score;
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
public Double getScore() { return this.score; }
public void setScore(String score) { this.score = score; }
}
class ExamResults {
private List<String> results = new ArrayList<>();
public void logResults( String name, Double score, boolean passed ) {
this.results.add(name + " scored " + score + "%, which is a " + (passed ? "passing": "failing") + " grade.");
}
public List<String> getResults() { return this.results; }
}
Rule:
global ExamResults results;
rule "Evaluate exam"
when
Student( $name: name )
Exam ( $score: score, name == $name )
then
boolean passed = $score > 60.0;
results.logResults( $name, $score, passed );
end
Invocation:
List<Student> students = ...;
List<Exam> exams = ... ;
ExamResults results = new ExamResults();
KieSession session = ruleBase.newStatefulSession();
students.forEach( student -> session.insert(students) );
exams.forEach( exam -> session.insert(exam) );
session.setGlobal( "results", results);
session.fireAllRules();
// Print the results:
results.getResults().forEach(System.out::println);
If all you're trying to do is to get some data out of your rules (eg whether certain conditions match), I've written up answers about how to do that previously here and here. If you just want to know what rules triggered, you should write a listener which logs rule hits ("afterMatchFired").

Spring data mongodb No mapping metadata found for java.util.Date result of aggregation pipeline

I have been getting a No mapping metadata found for java.util.Date exception as a result of an aggregation pipeline, here is the code:
Model:
public class Model {
// ...
private List<DateValue> dateValues = new ArrayList<>();
// setter and getters
}
public class DateValue {
private Date date;
private BigDecimal value;
// setter and getters
}
i then have a custom repository implementation that looks like this:
ModelRepositoryImpl:
public class ModelRepositoryImpl implements ModelRepositoryCustom {
// ...
#Override
public Date findMaxDate() {
final Direction direction = Direction.DESC;
final AggregationResults<Date> result = operations.aggregate( newAggregation( Model.class,
match( where( "dateValues" ).elemMatch( new Criteria().exists( true ) ) ),
sort( new Sort( direction, "dateValues.date" ) ),
limit( 1 ),
project().and( "dateValues.date" ).as( "date" ),
unwind( "date" ),
sort( new Sort( direction, "date" ) ),
limit( 1 ) )
, Date.class );
return result.getUniqueMappedResult();
}
}
this is the stacktrace: https://pastebin.ca/3947962
What am i missing?
Update
So I tried your example at home and got the same problem. I was sure there was an elegant way just couldn't test it earlier today. The thing is MongoDB returns Documents. Even if you just return the field date, it is still returned in that form:
{
"date" : ISODate("2017-12-14T19:00:00.000Z")
}
So all you have to do is create a new class that only contains a field Date. Here is my updated solution.
Create a class:
public class DateResult {
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
And then change your findMaxDate() to this:
public Date findMaxDate() {
final Direction direction = Direction.DESC;
final AggregationResults <DateResult> result = operations.aggregate(newAggregation(Model.class,
match(where("dateValues").elemMatch(new Criteria().exists(true))),
sort(new Sort(direction, "dateValues.date")), limit(1), project().and("dateValues.date").as("date"),
unwind("date"), sort(new Sort(direction, "date")), limit(1)), DateResult.class);
return result.getUniqueMappedResult().getDate();
}
Try adding #Document to your POJO class
Check out the official documentation
To take full advantage of the object mapping functionality inside the
Spring Data/MongoDB support, you should annotate your mapped objects
with the #Document annotation.
OR
What also helped for me in certain use cases was to use DBObject.class as result and then map them myself.
final AggregationResults<DBObject> result = operations.aggregate( newAggregation(
match( where( "dateValues" ).elemMatch( new Criteria().exists( true ) ) ),
sort( new Sort( direction, "dateValues.date" ) ),
limit( 1 ),
project().and( "dateValues.date" ).as( "date" ),
unwind( "date" ),
sort( new Sort( direction, "date" ) ),
limit( 1 ) )
, DBObject.class );
List<DBObject> dbObjectResults = result.getMappedResults();
or in your case since you set the limit to 1 you can also use:
DBObject dbObjectResult = result.getUniqueMappedResult();
Then you could get your values out of your BDObject
Date dateFromField = mapToDate(dbObjectResult .get("date")); // if your field is called "date"
private static Date mapToDate(Object document) {
if (document == null) {
return null;
}
if (document.getClass()
.equals(Date.class)) {
return (Date) document;
}
if (document.getClass()
.equals(DateTime.class)) {
return new DateTime(document).toDate();
}
return null;
}

Update facts in Decision Table : Drools

I have a drools decision table in excel spreadsheet with two rules. (This example has been greatly simplified, the one I am working with has alot more rules.)
The first rule checks if amount is more than or equal to 500. If it is, then it sets status to 400.
The second rule checks if status is 400. If it is, then it sets the message variable.
The problem is, I am unable to get the second rule to fire, even though sequential is set. I also have to use no-loop and lock-on-active to prevent infinite looping.
My goal is to get the rules to fire top down, and the rules that come after might depend on changes made to the fact/object by earlier rules.
Is there a solution to this problem?
Any help would be appreciated, thanks!
package com.example;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
public class SalaryTest {
public static final void main(String[] args) {
try {
// load up the knowledge base
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("ksession-dtables");
Salary a = new Salary();
a.setAmount(600);
kSession.insert(a);
kSession.fireAllRules();
} catch (Throwable t) {
t.printStackTrace();
}
}
public static class Salary{
private String message;
private int amount;
private int status;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
}
The attribute lock-on-active countermands any firings after the first from the group of rules with the same agenda group. Remove this column.
Don't plan to have rules firing in a certain order. Write logic that describes exactly the state of a fact as it should trigger the rule. Possibly you'll have to write
rule "set status"
when
$s: Salary( amount >= 500.0 && < 600.0, status == 0 )
then
modify( $s ){ setStatus( 400 ) }
end
to avoid more than one status setting to happen or just the right setting to happen. But you'll find that your rules may be more outspoken and easier to read.
Think of rule attributes are a last resort.
Please replace the action in the column H in the following way:
Current solution:
a.setStatus($param);update(a);
New solution:
modify(a) {
setStatus($param)
}

Stateless Vs Stateful Session behavior in drools

I am new to the drools. I was trying to understand the difference between Stateless and Stateful sessions provided by Drools.
As per my initial understanding,
In case of Stateless session, if fact is modified during action execution of any rule then it will not be re-submitted to inference engine to find out the new rules which matches the modified fact.
In case of Stateful session, if fact is modified during action execution of any rule then it will be re-submitted to inference engine to find out the new rules which matches the modified fact and then their corresponding action will be executed.
So when I tried to verify this behavior by writing a sample rule, I found that behavior is exactly same in both the cases. So now I am really confused regarding the difference between Stateful and Stateless sessions.
I would like to request everyone to help me in understand the correct behavior of Stateful and Stateless sessions.
For your reference I am pasting my sample code for Stateful and Stateless session along with their output and sample rule.
licenseApplication.drl (Rule file)
package com.idal.droolsapp
rule "Is of valid age"
no-loop
when
$a : Applicant( age < 18 )
then
System.out.println( "Not eligible for license" );
System.out.println( "Setting Valid to false" );
modify( $a ) { setValid( false ) };
end
rule "Is of valid false"
salience 100
when
$a : Applicant( valid == false )
then
System.out.println( "Second rule fired" );
end
Input object (Fact) Applicant.java
package com.idal.droolsapp;
public class Applicant {
private String name;
private int age;
private boolean valid = true;
public Applicant(String name, int age) {
setName(name);
setAge(age);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setValid(boolean valid) {
this.valid = valid;
}
public boolean isValid() {
return valid;
}
#Override
public String toString() {
return "Applicant [name=" + name + ", age=" + age + ", valid=" + valid
+ "]";
}
}
StatelessSessionExample.java (Stateless Session test code)
package com.idal.droolsapp;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactory;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.io.ResourceFactory;
import org.drools.runtime.StatelessKnowledgeSession;
public class StatelessSessionExample {
/**
* #param args
*/
public static void main(String[] args) {
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory
.newKnowledgeBuilder();
kbuilder.add(ResourceFactory.newClassPathResource(
"licenseApplication.drl", StatelessSessionExample.class),
ResourceType.DRL);
if (kbuilder.hasErrors()) {
System.err.println(kbuilder.getErrors().toString());
}
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
Applicant applicant = new Applicant( "Mr John Smith", 16 );
ksession.execute( applicant );
System.out.println("Updated Applicant = " + applicant);
}
}
Output of Stateless session test code:
Not eligible for license
Setting Valid to false
Second rule fired
Updated Applicant = Applicant [name=Mr John Smith, age=16, valid=false]
StatefulSessionExample.java (Stateless Session test code)
package com.idal.droolsapp;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactory;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.io.ResourceFactory;
import org.drools.runtime.StatefulKnowledgeSession;
public class StatefulSessionExample {
/**
* #param args
*/
public static void main(String[] args) {
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory
.newKnowledgeBuilder();
kbuilder.add(ResourceFactory.newClassPathResource(
"licenseApplication.drl", StatefulSessionExample.class),
ResourceType.DRL);
if (kbuilder.hasErrors()) {
System.err.println(kbuilder.getErrors().toString());
}
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
Applicant applicant = new Applicant( "Mr John Smith", 16 );
ksession.insert( applicant );
ksession.fireAllRules();
ksession.dispose();
System.out.println("Updated Applicant = " + applicant);
}
}
Output of Stateful session test code:
Not eligible for license
Setting Valid to false
Second rule fired
Updated Applicant = Applicant [name=Mr John Smith, age=16, valid=false]
Once again requesting everyone to help me in understanding the correct behavior of Stateful and Stateless sessions.
Thanks in advance,
Manish Gandhi
As I understand it when you fire the rules in a stateless session, changes won't trigger new rules. But that doesn't mean that rules won't be fired because of changes. The first rule changes the value of valid, which means that by the time you check the second rule's condition valid is already false and the rules fires.
If the change affected the "when" part of the previous rule, in a stateless session the first rule wouldn't be triggered a second time, where in the first one it would.

GWT SelectionModel is returning old selection

I have a cell table with an async data provider. If I update the data via the data provider the table renders the new data correctly but the selection model still holds onto and returns old objects.
Any ideas how to refresh the selection model?
I think you should make your SelectionModel work with different instance of the same "logical" object using the appropriate ProvidesKey. For instance, you could use ProvidesKey that calls getId on the object, so that two objects with the same such ID would be considered equal; so even if the SelectionModel holds onto the old object, it can still answer "yes, it's selected" when you give it the new object.
FYI, this is exactly what the EntityProxyKeyProvider does (using the stableId of the proxy). And the SimpleKeyProvider, used by default when you don't specify one, uses the object itself as its key.
I came across the same issue. Currently I have this as single selection model.
SelectedRow = store it when you select it.
Then when data is reloaded you can clear it by
celltable.getSelectionModel().setSelected(SelectedRow, false);
I guess it is too late for you but hope it helps someone else.
Here is my manual method for refreshing the SelectionModel. This allows you to use the selectedSet() when needed and it will actually contain the current data, rather than the old data - including the removal of deleted rows and updated fields!
I have included bits & pieces of a class extending DataGrid. This should have all the logic at least to solve your problems.
When a row is selected, call saveSelectionKeys().
When the grid data is altered call refeshSelectedSet().
If you know the key type, you can replace the isSameKey() method with something easier to deal with. This class uses generics, so this method attempts to figure out the object conversion itself.
.
public abstract class AsyncDataGrid<T> extends DataGrid<T> {
...
private MultiSelectionModel<T> selectionModel_;
private ListDataProvider<T> dataProvider_;
private List<T> dataList_;
private Set<Object> priorSelectionKeySet_;
private boolean canCompareKeys_;
...
public AsyncDataGrid( final ProvidesKey<T> keyProvider ){
super( keyProvider );
...
dataProvider_ = new ListDataProvider<T>();
dataList_ = dataProvider_.getList();
canCompareKeys_ = true;
...
}
private void saveSelectionKeys(){
priorSelectionKeySet_ = new HashSet<Object>();
Set<T> selectedSet = selectionModel_.getSelectedSet();
for( Iterator<T> it = selectedSet.iterator(); it.hasNext(); ) {
priorSelectionKeySet_.add( super.getValueKey( it.next() ) );
}
}
private void refeshSelectedSet(){
selectionModel_.clear();
if( priorSelectionKeySet_ != null ){
if( !canCompareKeys_ ) return;
for( Iterator<Object> keyIt = priorSelectionKeySet_.iterator(); keyIt.hasNext(); ) {
Object priorKey = keyIt.next();
for( Iterator<T> it = dataList_.iterator(); it.hasNext(); ) {
T row = it.next();
Object rowKey = super.getValueKey( row );
if( isSameKey( rowKey, priorKey ) ) selectionModel_.setSelected( row, true );
}
}
}
}
private boolean isSameRowKey( final T row1, final T row2 ) {
if( (row1 == null) || (row2 == null) ) return false;
Object key1 = super.getValueKey( row1 );
Object key2 = super.getValueKey( row2 );
return isSameKey( key1, key2 );
}
private boolean isSameKey( final Object key1, final Object key2 ){
if( (key1 == null) || (key1 == null) ) return false;
if( key1 instanceof Integer ){
return ( ((Integer) key1) - ((Integer) key2) == 0 );
}
else if( key1 instanceof Long ){
return ( ((Long) key1) - ((Long) key2) == 0 );
}
else if( key1 instanceof String ){
return ( ((String) key1).equals( ((String) key2) ) );
}
canCompareKeys_ = false;
return false;
}
}
I fixed my particular issue by using the following code to return the visible selection. It uses the selection model to determine what is selected and combines this with what is visible. The objects themselves are returned from the CellTable data which is always upto date if the data has ever been changed via an async provider (the selection model data maybe stale but the keys will be correct)
public Set<T> getVisibleSelection() {
/*
* 1) the selection model contains selection that can span multiple pages -
* we want to return just the visible selection
* 2) return the object from the cellTable and NOT the selection - the
* selection may have old, stale, objects if the data has been updated
* since the selection was made
*/
Set<Object> selectedSet = getKeys(selectionModel.getSelectedSet());
List<T> visibleSet = cellTable.getVisibleItems();
Set<T> visibleSelectionSet = new HashSet<T>();
for (T visible : visibleSet) {
if (selectedSet.contains(KEY_PROVIDER.getKey(visible))) {
visibleSelectionSet.add(visible);
}
}
return visibleSelectionSet;
}
public static Set<Object> getKeys(Collection<T> objects) {
Set<Object> ids = new HashSet<Object>();
for (T object : objects) {
ids.add(KEY_PROVIDER.getKey(object));
}
return ids;
}