I'm working on a small Drools project, because I want to learn more about using rule engines. I have a class called Event
that has the following fields:
String tag;
A tag which can be any string.long millis;
A timestamp. (Actually, this is converted from a JodaTime LocalDate
field which is also in Event
.) int value;
A value that I want reason about. I insert several hundreds of Event
instances into my knowledge base, and now I want to get the 3 most recent events that are tagged with "OK"
. I came up with the following code, which works:
rule "Three most recent events tagged with 'OK'"
when
$e1 : Event( tag == "OK",
$millis1 : millis )
$e2 : Event( tag == "OK",
millis < $millis1, $millis2 : millis )
$e3 : Event( tag == "OK",
millis < $millis2, $millis3 : millis )
not Event( tag == "OK",
millis > $millis1 )
not Event( tag == "OK",
millis > $millis2 && millis < $millis1 )
not Event( tag == "OK",
millis > $millis3 && millis < $millis2 )
then
# Do something with $e1.value, $e2.value and $e3.value
end
But I have a feeling there should be a better way to do this. This is quite verbose and not easily re-used: what if I want to get the five most recent events with value > 10
, for example? I would end up copy-pasting a lot of code, and I don't want to do that :). Also, the code doesn't look very 'beautiful' to me. I don't really like the repeated not Event...
constraints, and I also don't like having to repeat the same tag condition over and over. (This example is a heavily simplified version of my real app, in which the condition is actually a lot more complex.)
How can I improve this code?
Assuming you are using the STREAM event processing mode and your events are ordered in the stream:
rule "3 most recent events"
when
accumulate( $e : Event( tag == "OK" ) over window:length(3),
$events : collectList( $e ) )
then
// $events is a list that contains your 3 most recent
// events by insertion order
end
===== edit ====
Based on your comment bellow, here is how to achieve what you want in Drools 5.4+:
declare window LastEvents
Event() over window:length(3)
end
rule "OK events among the last 3 events"
when
accumulate( $e : Event( tag == "OK" ) from window LastEvents,
$events : collectList( $e ) )
then
// $events is a list that contains the OK events among the last 3
// events by insertion order
end
Just double check the syntax as I am doing this by heart, but it should be close to this.
I was able to simplify 'not logic' like this
rule "Three most recent events tagged with 'OK'"
when
$e1 : Event( tag == "OK")
$e2 : Event( tag == "OK", millis < $e1.millis )
$e3 : Event( tag == "OK", millis < $e2.millis )
not Event( this != $e2, tag == "OK", $e3.millis < millis, millis < $e1.millis )
then
System.out.printf("%s - %s - %s%n", $e1, $e2, $e3);
end
There was nothing said about cleaning events. Usually this is desirable, so you can achieve the same logic with deletion of the last event:
rule "Three most recent events tagged with 'OK'"
when
$e1 : Event( tag == "OK")
$e2 : Event( tag == "OK", millis < $e1.millis )
$e3 : Event( tag == "OK", millis < $e2.millis )
then
System.out.printf("%s - %s - %s%n", $e1, $e2, $e3);
retract ($e3)
end
Let say each second you'll insert an event one 'OK' another empty '', here is the test:
@DroolsSession("classpath:/test3.drl")
public class PlaygroundTest {
@Rule
public DroolsAssert drools = new DroolsAssert();
@Test
public void testIt() {
for (int i = 0; i < 10; i++) {
drools.advanceTime(1, SECONDS);
drools.insertAndFire(new Event(i % 2 == 0 ? "OK" : "", i));
}
}
}
all three variants will produce the same triggering logic:
00:00:01 --> inserted: Event[tag=OK,millis=0]
00:00:01 --> fireAllRules
00:00:02 --> inserted: Event[tag=,millis=1]
00:00:02 --> fireAllRules
00:00:03 --> inserted: Event[tag=OK,millis=2]
00:00:03 --> fireAllRules
00:00:04 --> inserted: Event[tag=,millis=3]
00:00:04 --> fireAllRules
00:00:05 --> inserted: Event[tag=OK,millis=4]
00:00:05 --> fireAllRules
00:00:05 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [Event, Event, Event]
OK4 - OK2 - OK0
00:00:06 --> inserted: Event[tag=,millis=5]
00:00:06 --> fireAllRules
00:00:07 --> inserted: Event[tag=OK,millis=6]
00:00:07 --> fireAllRules
00:00:07 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [Event, Event, Event]
OK6 - OK4 - OK2
00:00:08 --> inserted: Event[tag=,millis=7]
00:00:08 --> fireAllRules
00:00:09 --> inserted: Event[tag=OK,millis=8]
00:00:09 --> fireAllRules
00:00:09 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [Event, Event, Event]
OK8 - OK6 - OK4
00:00:10 --> inserted: Event[tag=,millis=9]
00:00:10 --> fireAllRules
variant with window:length(3)
will also deal with last 3 OK events. It is different on the beginning though: it will be triggered for 1 and 2 first OK events too. It will be also triggered once with empty list on the beginning if session doesn't contain any events. According to documentation , sliding windows start to match immediately and defining a sliding window does not imply that the rule has to wait for the sliding window to be "full" in order to match. For instance, a rule that calculates the average of an event property on a window:length(10) will start calculating the average immediately, and it will start at 0 (zero) for no-events, and will update the average as events arrive one by one.
00:00:01 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [InitialFactImpl, UnmodifiableRandomAccessList]
[]
00:00:01 --> inserted: Event[tag=OK,millis=0]
00:00:01 --> fireAllRules
00:00:01 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [InitialFactImpl, UnmodifiableRandomAccessList]
[OK0]
00:00:02 --> inserted: Event[tag=,millis=1]
00:00:02 --> fireAllRules
00:00:03 --> inserted: Event[tag=OK,millis=2]
00:00:03 --> fireAllRules
00:00:03 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [InitialFactImpl, UnmodifiableRandomAccessList]
[OK0, OK2]
00:00:04 --> inserted: Event[tag=,millis=3]
00:00:04 --> fireAllRules
00:00:05 --> inserted: Event[tag=OK,millis=4]
00:00:05 --> fireAllRules
00:00:05 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [InitialFactImpl, UnmodifiableRandomAccessList]
[OK0, OK2, OK4]
00:00:06 --> inserted: Event[tag=,millis=5]
00:00:06 --> fireAllRules
00:00:07 --> inserted: Event[tag=OK,millis=6]
00:00:07 --> fireAllRules
00:00:07 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [InitialFactImpl, UnmodifiableRandomAccessList]
[OK2, OK4, OK6]
00:00:08 --> inserted: Event[tag=,millis=7]
00:00:08 --> fireAllRules
00:00:09 --> inserted: Event[tag=OK,millis=8]
00:00:09 --> fireAllRules
00:00:09 <-- 'Three most recent events tagged with 'OK'' has been activated by the tuple [InitialFactImpl, UnmodifiableRandomAccessList]
[OK4, OK6, OK8]
00:00:10 --> inserted: Event[tag=,millis=9]
00:00:10 --> fireAllRules
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.