Adding rule condition based on two objects in drools - drools

> { "batch-execution":{
> "lookup":"defaultKieSession",
> "commands":[
> {
> "insert":{
> "out-identifier":"FieldData1",
> "object":{
> "FieldData":{
> "name":"abc",
> "value":"111"
> }
> }
> }
> },
> {
> "insert":{
> "out-identifier":"FieldData2",
> "object":{
> "FieldData":{
> "name":"xyz",
> "value":"222"
> }
> }
> }
> },
> {
> "fire-all-rules":{
>
> }
> }
> ] } }
Now i want to write a condition in drl similar to this:
rule "testrule"
when
fieldData(name == "abc" , value == "111") && fieldData(name == "xyz",value = "222")
then
System.out.println("Condition executed")
Can someone help on how this can be done in drools ?

Of course you can! Your "example" rule is nearly perfect as-is.
The way drool works is that it evaluates the conditions of all of the objects in working memory, and only if all conditions are met will it trigger that rule.
So you could write a rule that looks like this:
rule "Test Rule"
when
exists( FieldData( name == "abc", value == "111") )
exists( FieldData( name == "xyz", value == "222") )
then
System.out.println("Condition Executed")
end
This rule will trigger if there exists an object in working memory that has a name of 'abc' and a value of '111', and there also exists an object in working memory with the name of 'xyz' and value of '222'.
In the example above, I used the 'exists' predicate because we weren't going to actually be doing anything with those values, and we just wanted to confirm that there is such a FieldData object in memory that matches the required conditions.
Note that this assumes that you've entered your FieldData objects directly into the working memory as standalone objects. (Eg you fired the rules and passed it a List of FieldData.) If you're working with bigger structures, you'll have to extract the FieldData objects, and then do an exists check like I had in my previous example.
For example, let's say you had a set of classes like this (which mimic your example JSON; getters and setters omitted for brevity):
class FieldData {
String name;
String value;
}
class Command {
String outIdentified;
FieldData object;
}
class BatchExecution {
String action; // eg "insert"
String lookup;
List<Command> commands;
}
If you passed a BatchExecution into the rules, you'll need to pull the field data out of the commands before you can check that two FieldData exist with the conditions you want. Your rule would therefore look more like this:
rule "Test Rule 2"
when
// Get the BatchExecution in working memory and pull out the Commands
BatchExecution( $commands: commands != null )
// Get the FieldData from each of the commands
$data: ArrayList( size >= 2) from
accumulate( Command( $fd: object != null ) from $commands,
init( ArrayList fieldDatas = new ArrayList() ),
action( fieldDatas.add( $fd ) ),
reverse( fieldDatas.remove( $fd ) ),
result( fieldDatas ))
// Confirm there exist FieldData with our criteria inside of the Commands
exists( FieldData( name == "abc", value == "111" ) from $data
exists( FieldData( name == "xyz", value == "222" ) from $data
then
System.out.println("Condition executed, 2");
end
Basically what we have to do is drill down from the object actually inserted into working memory until we can get to the objects that we need to be doing work against, in this case the FieldData. I used the accumulate aggregate to pull all of the FieldData into a List that we then check for the presence of the two FieldData that we're looking for in this particular rule.
For more information, you should consider reading the Drools documentation, specifically the part on the "Rule Language Reference" (section 4) which is very well written and contains plenty of examples that you can adapt or expand upon.

The rule firing in drools only happens on the occurrence of some event. Read about sessions and execution of rules here.
Coming to the your question of writing the above rule. I am not sure how you want the rule to be executed. As per my understanding if you want to write a separate rule each to check name as xyz and abc, then you can write the rule as below:
rule "testrule1"
when
fieldData(name == "abc" , value == "111")
then
System.out.println("Condition executed 1")
end
rule "testrule2"
when
fieldData(name == "xyz" , value == "222")
then
System.out.println("Condition executed2")
end
If you want to combine the rule then you can write it as:
rule "testrule"
when
fieldData(name == "abc" || name == "xyz" && value == "111" || value == "222")
then
System.out.println("Condition executed")
end
Note: You cannot define a rule like above if you want to work on the occurrence of multiple events. If you want to work on multiple events you can read about windowing in drools. Add more information on your use case to get better answers.

Related

Optaplanner: Iterate over list variable in drools rule file

I am solving a problem similar to employee rostering. I have an additional constraint. The employees have a "type" value assigned to them. It's a hard constraint that atleast 1 employee of each "type" be there everyday. I have modelled it as follows:
rule "All employee types must be covered"
when
$type: Constants.EmployeeType() from Constants.EmployeeType.values()
not Shift(employeeId != null, $employee: getEmployee(), $employee.getType() == $type.getValue())
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
This rule however, does not consider that the constraint be satisfied on each day. I have a list of date strings. How can I iterate over them in the drools file in the same manner that I am on the EmployeeType enum?
Edit: I figured out a way but it feels like a hack. When initialising the list of date strings, I also assign it to a static variable. Then I am able to use the static variable similar to the enum.
rule "All employee types must be covered"
when
$type: Constants.EmployeeType() from Constants.EmployeeType.values()
$date: String() from Constants.dateStringList;
not Shift(employeeId != null, $date == getDate(), $employee: getEmployee(), $employee.getType() == $type.getValue())
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
Don't think this is the correct approach though.
Your approach works, but having to define dynamic configurations in a static property of a class doesn't sound right (like you pointed out).
One solution would be to either use a global in the session, or to have a fact class that specify this configuration.
Using a global
If you decide to take this approach, then you need to define a global of type List<String> in your DRL and then use it in your rules in combination with the memberOf operator:
global List<String> dates;
rule "All employee types must be covered"
when
$type: Constants.EmployeeType() from Constants.EmployeeType.values()
not Shift(
employeeId != null,
date memberOf dates,
$employee: getEmployee(),
$employee.getType() == $type.getValue()
)
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
It is recommended to set the value for global before you insert any fact Shift into you session:
List<String> dates = //get the List from somewhere
ksession.setGlobal("dates", dates);
Using a Fact Class
Other than a global, you can model your configuration as a class. This makes things easier if you want for example to modify the configuration inside the rules themselves.
for this approach you will need to have a class containing the List<String> first. You could in theory insert the List<String> without wrapping it in any class, but this will make things hard to read and maintain.
public class DatesConfiguration {
private List<String> dates;
//... getters + setters
}
Then, you need to instantiate an object of this class and to insert it into your session:
DatesConfiguration dc = new DatesConfiguration();
dc.setDates(...);
ksession.insert(dc);
At this point, the object you have created is just another fact for Drools and can be used in your rules:
rule "All employee types must be covered"
when
$type: Constants.EmployeeType() from Constants.EmployeeType.values()
DatesConfiguration($dates: dates)
not Shift(
employeeId != null,
date memberOf $dates,
$employee: getEmployee(),
$employee.getType() == $type.getValue()
)
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
Hope it helps,

Using enum in drools

I am solving an employee rostering problem. One of the constraints there is that Employee from each "type" should be present on every day. Type is defined as an enum.
I have right now configured this rule as follows:
rule "All employee types must be covered"
when
not Shift(employeeId != null, $employee: getEmployee(), $employee.getType() == "Developer")
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
This works fine. However I would have to configure a similar rule for all possible employee types.
To generalize it, I tried this:
rule "All employee types must be covered"
when
$type: Constants.EmployeeType()
not Shift(employeeId != null, $employee: getEmployee(), $employee.getType() == $type.getValue())
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
However, this rule doesn't get executed. Below is my enum defined in Constants file
public enum EmployeeType {
Developer("Developer"),
Manager("Manager");
private String value;
Cuisine(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
What am I doing wrong?
I guess the problem is that you are never inserting the enums in your session (they are not facts).
One way to solve it is to manually insert them:
for(EmployeeType type : Constants.EmployeeType.values()){
ksession.insert(type);
}
Another way is to make your rule fetch all the possible values from the enum:
rule "All employee types must be covered"
when
$type: Constants.EmployeeType() from Constants.EmployeeType.values()
not Shift(employeeId != null, $employee: getEmployee(), $employee.getType() == $type.getValue())
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
Hope it helps,

Search element in List in Drools

I have list of objects Person. Object Person contain list of objects Car. How i can select from list only thoose Person, who contain Car selected type. For exmplain: Car with brand "BMW". I don't know do it without for loop.
person[0].addCar(new Car("BMW"));
person[0].addCar(new Car("Ford"));
person[1].addCar(new Car("Ford"));
person[1].addCar(new Car("Ford"));
person[1].addCar(new Car("Ford"));
How i can return person[0] in drools-regulations.
My code doesn't work.
rule "HardDrool"
salience 100
when
$world : World();
$persons: Human(
(name == "Yura"),
(!cars.isEmpty()),
(Car(name == "BMW") from getCars())
) from $world.getPersons()
then
System.out.println($persons);
end
rule "HardDrool"
when
$world : World();
$person: Human( name == "Yura", !cars.isEmpty() )
from $world.getPersons()
exists Car( name == "BMW" ) from $person.getCars())
then
System.out.println( $person );
end
This should fire once for each Human owning at least one BMW. If you want to see each BMW, omit the exists.

Insertions/evaluations are progressively slower when using temporal rule

I have a rule that looks for 2 consecutive events for the same entity. To stress test it, I inserted 10K consecutive events. I'm calling fireAllRules() after each event is inserted. I'm printing out timestamps after every 100 events. I'm noticing that insertions/evaluations are increasingly slower as events are added. Here's the rule :
rule "Consecutive events"
when
$latest : Event($id : id) // newest event
not Event(id == $id, this after $latest) // no events after the newest
$previous : Event(id == $id, this before $latest) // event before the newest
not Event(id == $id, this before $latest, this after $previous) // no events between $latest and $previous
then
//System.out.println($latest.toString());
end
It is my understanding that the above rule should only match the latest 2 events and automatic memory management should remove older events. If so, why are insertions progressively slower? Interestingly, ksession.getObjects() returns all the events inserted and not just the latest 2 events. Is my understanding of the rule incorrect? Does the rule somehow force all events to stay in memory? I'm using v6.0.1.Final.
Your rule does not define a time limit for automatic retraction as the before and after are not limited.
There are several ways to keep the number of events low, which is the reason for inserts getting progressively slower. Here is a simple technique:
declare Pair
one : Event
two : Event
id : String
end
rule "create Pair"
when
$e: Event( $id: id )
not Pair( id == id )
then
insert( new Pair( null, $e, $id ) );
end
rule "another Pair"
when
$e2: Event( $id: id )
$p: Pair( $e0: one, $e1: two != $e2, id == $id )
then
modify( $p ){
setOne( $e1 ),
setTwo( $e2 ) }
retract( $e0 ); // optional
// process pair $e1, $e2
end

Using collect in drools to get similar objects

I am trying to collect some objects in Drools, but I want to only collect objects which have the same attribute. To wit, imagine a TestData class:
public class TestData {
int number;
String name;
//Getters, setters, rest of class
}
I want to use collect to get all TestDatas which have the same name. For the following data set, I want a rule which can collect the first two (both having the name 'Test1') and the second two ('Test2') as separate collections.
custom.package.TestData< number: 1, name:'Test1' >
custom.package.TestData< number: 2, name:'Test1' >
custom.package.TestData< number: 3, name:'Test2' >
custom.package.TestData< number: 4, name:'Test2' >
Something like
rule "Test Rule"
when
$matchingTestDatas : ArrayList( size > 1 ) from collect ( TestData( *magic* ) )
then
//Code
end
But obviously that won't work without the magic- it gets me an array of all the TestData entries with every name. I can use that as the basis for a rule, and do extended processing in the right hand side iterating over all the test data entries, but it feels like there should be something in drools which is smart enough to match this way.
Presumably the "magic" is just:
TestData( name == 'Test1' )
or
TestData( name == 'Test2' )
... for each of the collections. But that seems too obvious. Am I missing something?
Based on the clarification from the OP in the comments on this answer, it would appear that a Map of collections is required, keyed from the name. To support this, accumulate is required, rather than collect.
$tests: HashMap()
from accumulate( $test : TestData( name == $name ),
init( HashMap tests = new HashMap(); ),
action(
if (tests.get($name) == null) tests.put($name, new ArrayList();
tests.get($name).add($test);
),
reverse(
tests.get($name).remove($test);
),
result( tests )
);