Drools 规则 - 空检查和累积条件

Drools rule - null check and accumulate condition

我目前正在对 java 批次进行一些修复,其中 运行 一组 Drools(耶!)规则。

我必须修正的规则是:

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

为了简化,Person 是一个带有 personLastDatas Set<LastData>LastData 具有 org.joda.time.DateTime [= 的简单 bean 28=]lastDate 属性.

问题:如何在 $person.personLastDatas 为空时应用规则的情况下插入新条件?

类似于:

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

您应该有两个规则,一个用于空条件,一个用于比较日期。

这里是空条件规则;它验证 Person 存在但它没有 personLastDatas 属性:

rule "Insert a too old condition modified - null case"
salience -1
when
  $person: Person( personLastDatas == null )
then
  insert(new Condition("submitTooOldCondition"));
end

您现有的规则足以进行日期比较检查。

一般来说,如果您发现自己试图在规则的任一侧执行复杂的 if-else 逻辑,这很好地表明您应该有两个规则。由于这两条规则不能同时为真,您只能插入一个此类条件。

也就是说,如果您使用的是现代版本的流口水,则可以使用条件和命名空间结果。 documentation 对此进行了详细介绍(我已链接 7.37.0.Final;大多数最新的 7.30+ 版本都具有此功能。)下面是您的规则的示例:

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

(这是伪代码;我这台电脑上没有drools项目,但应该是类似的。)

基本上,这种语法虽然难以阅读,但可以让您处理这些类型的 repetitive/partial 共享大小写规则。通常建议在您有两个规则的情况下使用它们,其中一个规则扩展另一个规则,因此常见条件的子集可以触发一个结果,而完整的条件集可以触发另一个结果。这与您在这里所拥有的不完全相同,但可以针对您的用例对功能进行混杂处理。

break 关键字告诉引擎一旦满足条件就停止评估左侧;还有一个 do 关键字,允许继续评估。

选项 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

选项 2

使聚合函数按您需要的方式工作。 根据您在规则中表达的业务,null DateTime 应被视为尽可能低的值。如果所有其他规则都是如此,您可以将此逻辑封装在您的 maxValue 函数。

public Object getResult(HashSet<DateTime> context) throws Exception {
    return context.isEmpty() ? new DateTime(0) /*null*/ : context.iterator().next();
}

根据上述逻辑,您的原始规则无需任何修改即可按预期工作。


测试

@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));
    }
}

测试输出

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

函数来源

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;
    }
}

规则来源

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