Reputation: 5828
I'm currently doing some fix on a java batch which run a set of Drools
(yeuch!) rules.
The rule I have to fix is this:
rule "Insert a too old condition"
salience -1
when
$person : Person()
$tooOldInstant : DateTime() from now.minusDays(10)
DateTime( this < $tooOldInstant ) from accumulate (
LastData( $date : lastDate ) from $person.personLastDatas,
maxValue($date)
)
then
insert(new Condition("submitTooOldCondition"));
end
where for simplification Person
is a simple bean with a personLastDatas Set<LastData>
AND LastData
has a org.joda.time.DateTime
lastDate property.
Question: How do I insert a new condition where if $person.personLastDatas
is null the rule apply?
Something like:
rule "Insert a too old condition modified"
salience -1
when
$person : Person()
$tooOldInstant : DateTime() from now.minusDays(10)
$maxLastDate : DateTime() from accumulate (
LastData( $date : lastDate ) from $person.personLastDatas,
maxValue($date)
)
($maxLastDate == null || $maxLastDate < $tooOldInstant)
then
insert(new Condition("submitTooOldCondition"));
end
Upvotes: 2
Views: 2037
Reputation: 20966
Option 1
rule "Insert a too old condition"
salience -1
when
Person(personLastDatas == null)
or
$person : Person()
and $tooOldInstant : DateTime() from now.minusDays(10)
and DateTime( this < $tooOldInstant ) from accumulate (
LastData( $date : lastDate ) from $person.personLastDatas,
maxValue($date)
)
then
System.out.println("submitTooOldCondition");
end
Option 2
Make your aggregate function work the way you need. From the business you expressed in the rule, null DateTime should be treated as a lowest possible value. If this is true for all other rules, you can encapsulate this logic in your maxValue
function.
public Object getResult(HashSet<DateTime> context) throws Exception {
return context.isEmpty() ? new DateTime(0) /*null*/ : context.iterator().next();
}
With the logic above your original rule will work as expected without any modification.
test
@DroolsSession(resources = "classpath:/test.drl")
public class PlaygroundTest {
@Rule
public DroolsAssert drools = new DroolsAssert();
@Test
@TestRules(expectedCount = { "2", "Insert a too old condition" })
public void testIt() {
drools.setGlobal("now", now());
drools.insertAndFire(new Person(newHashSet(new LastData(now().minusDays(100)), new LastData(now().minusDays(5)))));
drools.insertAndFire(new Person(newHashSet(new LastData(now().minusDays(100)), new LastData(now().minusDays(15)))));
drools.insertAndFire(new Person(null));
}
}
test output
00:00:00 --> inserted: Person[personLastDatas=[org.droolsassert.LastData@25243bc1, org.droolsassert.LastData@1e287667]]
00:00:00 --> fireAllRules
00:00:00 --> inserted: Person[personLastDatas=[org.droolsassert.LastData@76f10035, org.droolsassert.LastData@5ab9b447]]
00:00:00 --> fireAllRules
00:00:00 <-- 'Insert a too old condition' has been activated by the tuple [Person, DateTime, DateTime]
submitTooOldCondition
00:00:00 --> inserted: Person[personLastDatas=<null>]
00:00:00 --> fireAllRules
00:00:00 <-- 'Insert a too old condition' has been activated by the tuple [Person]
submitTooOldCondition
function source
public class MaxValueAccumalateFunction implements AccumulateFunction<HashSet<DateTime>> {
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
@Override
public HashSet<DateTime> createContext() {
return new HashSet<>();
}
@Override
public void init(HashSet<DateTime> context) throws Exception {
}
@Override
public void accumulate(HashSet<DateTime> context, Object value) {
if (context.isEmpty() || context.iterator().next().isBefore((DateTime) value)) {
context.clear();
context.add((DateTime) value);
}
}
@Override
public void reverse(HashSet<DateTime> context, Object value) throws Exception {
}
@Override
public Object getResult(HashSet<DateTime> context) throws Exception {
return context.isEmpty() ? new DateTime(0) /*null*/ : context.iterator().next();
}
@Override
public boolean supportsReverse() {
return false;
}
@Override
public Class<?> getResultType() {
return null;
}
}
rule source
import org.joda.time.DateTime;
import accumulate org.droolsassert.MaxValueAccumalateFunction maxValue;
global DateTime now;
rule "Insert a too old condition"
salience -1
when
Person(personLastDatas == null)
or
$person : Person()
and $tooOldInstant : DateTime() from now.minusDays(10)
and DateTime( this < $tooOldInstant ) from accumulate (
LastData( $date : lastDate ) from $person.personLastDatas,
maxValue($date)
)
then
System.out.println("submitTooOldCondition");
end
Upvotes: 1
Reputation: 15180
You should have two rules, one for the null condition and one for the one that compares the dates.
Here is the null condition rule; it verifies that the Person exists but that it has no personLastDatas
property:
rule "Insert a too old condition modified - null case"
salience -1
when
$person: Person( personLastDatas == null )
then
insert(new Condition("submitTooOldCondition"));
end
Your existing rule is sufficient for the date comparison check.
In general, if you find yourself trying to do complex if-else logic on either side of the rule, it's a good indication that you should have two rules. Since these two rules cannot both be true, you'll only have one condition of this type inserted.
That being said, if you're using a modern version of drools, you can use conditional and namespaced consequences. The documentation covers this in detail (I've linked 7.37.0.Final; most of the recent 7.30+ versions have this functionality.) Below is an example of what your rule might look like:
rule "Insert a too old condition"
salience -1
when
$person : Person( $lastDatas: personLastDatas )
if ( $lastDatas == null ) break[noData]
$tooOldInstant : DateTime() from now.minusDays(10)
DateTime( this < $tooOldInstant ) from accumulate (
LastData( $date : lastDate ) from $person.personLastDatas,
maxValue($date)
)
then
insert(new Condition("submitTooOldCondition"));
then[noData]
// any special logic for the null case goes here
insert(new Condition("submitTooOldCondition"));
end
(This is pseudo-code; I don't have a drools project on this computer but it should be similar.)
Basically this syntax, though harder to read, will allow you to handle these sorts of repetitive/partial shared case rules. They're usually recommended for cases where you have two rules, where one extends the other, so a subset of the common conditions can trigger one consequence, while the full set of the conditions can trigger another consequence. That's not quite what you have here, but the functionality can be bastardized for your use case.
The break
keyword tells the engine to stop evaluating the left hand side once the condition is met; there's also a do
keyword which allows for continued evaluation.
Upvotes: 1