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.
Related
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").
Please have a look at the two code settings below.
Setting 1:
public class DroolsAnotherTest {
private static KieSession kSession;
private static Building building;
private static FactHandle buildingFact;
public static final void main(String[] args) {
try {
// load up the knowledge base
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
kSession = kContainer.newKieSession("ksession-rules");
building = new Building();
building.setRooms(2);
buildingFact = kSession.insert(building);
kSession.fireAllRules();
} catch (Throwable t) {
t.printStackTrace();
}
}
public static void fireAgain(){
System.out.println("inside fireAgain method");
kSession.fireAllRules(); // firing the rules second time
}
public static class Building {
public int rooms;
public int getRooms() {
return this.rooms;
}
public void setRooms(int rooms) {
this.rooms = rooms;
}
}
}
And the .drl file
package com.sample
import com.sample.DroolsAnotherTest.Building;
import com.sample.DroolsAnotherTest;
rule "Building"
when
Building( rooms <3 )
then
System.out.println( "Building rule fired" );
DroolsAnotherTest.firingAgain();
end
Upon running, the output is :
Building rule fired
inside fireAgain method
Setting 2:
Here I just changed the fireAgain() method to:
public static void fireAgain(){
System.out.println("inside fireAgain method");
kSession.delete(buildingFact);
building.setRooms(4);
kSession.insert(building);
kSession.fireAllRules(); // firing the rule second time
}
everything else is same.
Upon running, the output is same again :
Building rule fired
inside fireAgain method
As per my understanding,
in Setting 1, the rule did not get fired second time because the fact has not changed in the working memory.
in Setting 2, the rule did not get fired second time because now the fact has updated and it does not match with the rule condition.
My question is, does Drools generate any event for the setting 2 ( the rule fired once but now is not fired again because fact has updated ?) In that way I can distinguish between : a rule did not get fired because the fact are unchanged and it did not get fired because now the fact does not match with rule condition ?
There is no way you can determine why a rule isn't put on the agenda.
You know when you update a fact, so if this interesting for some reason, register it in the fact or elsewhere.
And DO NOT, repeat: DO NOT, call fireAllRules while another such call on the same session is still executing. There may be all kind of unexpected effects.
, i have a #Test method with dataprovider , i have a Listener.class , my target is that when my #Test method is success, the status of case in testrail is setted as "Passed" automatically in onTestSuccess Method of Listener class , this process is ok, but when i use dataprovider for #Test Method, this causes the problem
i want that same method must be worked (let say) three times because of dataprovider and different case id data must be sent to onTestSuccess method for each iteration from #Test method
My Listener.class
package com.myproject.test.listeners;
import java.lang.reflect.Method;
import org.testng.IClass;
import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ISuite;
import org.testng.ISuiteListener;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
public class Listener implements ITestListener, ISuiteListener, IInvokedMethodListener {
...
public void onTestStart(ITestResult result) {
}
public void onTestSuccess(ITestResult result) {
try {
Program pr = new Program();
System.out.println("onTestSuccess Method for :" + result.getName());
String TestID = null;
String TestRunID = null;
for (Method testMethod : result.getTestClass().getRealClass().getMethods()) {
if (testMethod.getName().equals(result.getName()) && testMethod.isAnnotationPresent(UseAsTestRailId.class)) {
UseAsTestRailId useAsTestName = testMethod.getAnnotation(UseAsTestRailId.class);
TestID = Integer.toString(useAsTestName.testRailCaseId());
TestRunID = Integer.toString(useAsTestName.testRailRunId());
System.out.println("Case ID---> " + TestID + " Run ID--> " + TestRunID);
// 1 = Passed
pr.enterTestResult(TestRunID, TestID, 1);
}
}
} catch (Exception ex) {
System.out.println(ex.toString());
}
}
...
}
My test class (SettingsTests.java) including my #Test method (checkCurrentPasswordFormatIsValidatedTest)
#Listeners(com.test.listeners.Listener.class)
//listener annotation row is written in BaseTest class
public class SettingsTests extends BaseTest {
...
/**
* Test Case - C5001275 - Check that "Please enter at least 8 characters."
* message is displayed when entered value into "Current Password" field in
* wrong format This case will run two times!
*
* #param currentPasswordValue
*/
#Test(dataProvider = "currentPasswordTestWithWrongValue")
#UseAsTestRailId(testRailCaseId = 5001275,testRailRunId = 56662)
// aim is that to send different case id for each iteration,now even if method works twice , only one testRailCaseId is sent
public void checkCurrentPasswordFormatIsValidatedTest(String currentPasswordValue) {
logger.trace("STARTING TEST: checkCurrentPasswordFormatisValidatedTest");
logger.trace("Test Step : Enter current password in wrong format");
settingsPageObject.enterCurrentPassword(currentPasswordValue);
logger.trace("Test Step : Click on the button 'UPDATE' ");
settingsPageObject.clickOnUpdateButton();
logger.trace("Expected Result: The message 'Please enter at least 8 characters.' is displayed on screen.");
Assert.assertEquals(settingsPageObject.getCurrentPasswordWrongText(), "Please enter at least 8 characters.");
}
#DataProvider(name = "currentPasswordTestWithWrongValue")
public static Object[][] validateTestWithCurrentPasswordInWrongFormat() {
return new Object[][] { { RandomStringUtils.randomAlphabetic(7) }, { RandomStringUtils.randomAlphabetic(1) } };
}
...
}
My annotation class (UseAsTestRailId.java)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface UseAsTestRailId
{
int testRailCaseId() default 0;
int testRailRunId() default 0;
String[] tags() default "";
}
Some #Test methods to set two case status, some #Test methods to set three case status in testRail,so the more dataprovider data set needed the more case id needed , it must be dynamical
You can use the setattribute value in the testresult object to set custom value. Get the currentresult from Reporter : Reporter.getCurrentTestresult and then setAttribute ("TC_id",sasdf) and use that in your ontestsuccess using the getAttribute ("TC_id") on result object.
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)
}
Facing Problem with Iteration of List in drl file. I need to Retrieve each HashMap object and check for 'Issue' key. If value of 'issue' is not empty then need to add a value to 'alert' key.
public class ReservationAlerts {
public enum AlertType {
RESERVATIONDETAILSRESPONSE
}
#SuppressWarnings("rawtypes")
private List<HashMap> reservationMap;
#SuppressWarnings("rawtypes")
public List<HashMap> getReservationMap() {
return reservationMap;
}
#SuppressWarnings("rawtypes")
public void setReservationMap(List<HashMap> reservationMap) {
this.reservationMap = reservationMap;
}
}
Main Java Program:
DroolsTest.java
public class DroolsTest {
#SuppressWarnings("rawtypes")
public static final void main(String[] args) {
try {
// load up the knowledge base
KnowledgeBase kbase = readKnowledgeBase();
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
ReservationAlerts rAlerts = new ReservationAlerts();
List<HashMap> hashMapList = new ArrayList<>();
HashMap<String, String> hMap = new HashMap<>();
hMap.put("rId", "101");
hMap.put("fName", "ABC");
hMap.put("lName", "DEF");
hMap.put("issue", "1qaz");
hMap.put("alert", "");
hashMapList.add(hMap);
HashMap<String, String> hMapI = new HashMap<>();
hMapI.put("rId", "102");
hMapI.put("fName", "GHI");
hMapI.put("lName", "JKL");
hMapI.put("issue", "");
hMapI.put("alert", "");
hashMapList.add(hMapI);
rAlerts.setReservationMap(hashMapList);
System.out.println("**********BEFORE************");
System.out.println(hMap.keySet());
System.out.println("****************************");
System.out.println(hMapI.keySet());
System.out.println("****************************");
ksession.insert(rAlerts);
ksession.fireAllRules();
.............
Need to update the HashMap and return the updated List from drl file. Can any one Help me plz
Drool File being triggered from Java File
Reservations.drl
import com.dwh.poc.ReservationAlerts;
import java.util.List;
import java.util.HashMap;
import java.util.Iterator;
// declare any global variables here
dialect "java"
rule "Reservation Alert"
// Retrieve List from ReservationAlerts
when
$rAlerts : ReservationAlerts()
$alertsMapList : List() from $rAlerts.reservationMap
then
// Iterate List and retrieve HashMap object
for(Iterator it = $alertsMapList.iterator();it.hasNext();) {
$alertMap : it.next();
}
The from in your rule will automatically loop over the list returned by $rAlerts.reservationMap. This means that the pattern you need to use in the left hand side of your from is Map and not List.
Once you have the Map pattern you can add the constraint about the 'issue' key.
Try something like this:
rule "Reservation Alert"
when
$rAlerts : ReservationAlerts()
$map : Map(this["issue"] != "") from $rAlerts.reservationMap
then
$map.put("alert", "XXXX");
end
Hope it helps,