Drools规则 – 空值检查和累积条件

huangapple go评论76阅读模式
英文:

Drools rule - null check and accumulate condition

问题

我目前正在对一个运行一组Drools(呃!)规则的Java批处理进行一些修复。

我需要修复的规则如下:

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属性的简单Bean,而LastData具有org.joda.time.DateTimelastDate属性。

问题:如果$person.personLastDatas为null,如何插入一个新条件使规则生效?

类似于:

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
英文:

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 &quot;Insert a too old condition&quot;
        salience -1
    when
        $person : Person()
        $tooOldInstant : DateTime() from now.minusDays(10)
        DateTime( this &lt; $tooOldInstant ) from accumulate (
            LastData( $date : lastDate ) from $person.personLastDatas,
            maxValue($date)
        )
    then
        insert(new Condition(&quot;submitTooOldCondition&quot;));
end

where for simplification Person is a simple bean with a personLastDatas Set&lt;LastData&gt; 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 &quot;Insert a too old condition modified&quot;
        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 &lt; $tooOldInstant)
    then
        insert(new Condition(&quot;submitTooOldCondition&quot;));
end

答案1

得分: 1

你应该有两个规则,一个用于处理空条件,另一个用于比较日期。

以下是空条件规则;它验证了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 逻辑,那是一个很好的指示,你应该有两个规则。因为这两个规则不能同时为真,你只会插入这种类型的一种条件。

话虽如此,如果你正在使用现代版本的 Drools,你可以使用条件和命名空间后果。文档详细介绍了这个功能(我链接的是 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 项目,但应该类似。)

基本上,尽管这种语法可能更难阅读,但它将允许你处理这些重复/部分共享的案例规则。通常建议在一个规则扩展另一个规则的情况下使用它们,这样共同条件的子集可以触发一个结果,而条件的完整集合可以触发另一个结果。虽然这与你在这里的情况不完全相同,但该功能可以为你的用例定制。

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

英文:

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 &quot;Insert a too old condition modified - null case&quot;
salience -1
when
  $person: Person( personLastDatas == null )
then
  insert(new Condition(&quot;submitTooOldCondition&quot;));
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 &quot;Insert a too old condition&quot;
salience -1
when
  $person : Person( $lastDatas: personLastDatas )
  if ( $lastDatas == null ) break[noData]
  $tooOldInstant : DateTime() from now.minusDays(10)
  DateTime( this &lt; $tooOldInstant ) from accumulate (
            LastData( $date : lastDate ) from $person.personLastDatas,
            maxValue($date)
  )
then
  insert(new Condition(&quot;submitTooOldCondition&quot;));
then[noData]
  // any special logic for the null case goes here
  insert(new Condition(&quot;submitTooOldCondition&quot;));
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.

答案2

得分: 1

Option 1

规则 "插入过时条件"
        优先级 -1
        Person(personLastDatas == null)
        $person : Person()
        并且 $tooOldInstant : DateTime() from now.minusDays(10)
        并且 DateTime( this < $tooOldInstant ) from accumulate (
            LastData( $date : lastDate ) from $person.personLastDatas,
            maxValue($date)
        )
        System.out.println("提交过时条件");
end

Option 2

使您的聚合函数按您需要的方式工作_根据规则中表达的业务空的 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", "插入过时条件" })
    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 --> 触发所有规则
00:00:00 --> inserted: Person[personLastDatas=[org.droolsassert.LastData@76f10035, org.droolsassert.LastData@5ab9b447]]
00:00:00 --> 触发所有规则
00:00:00 <-- '插入过时条件' 已被元组 [Person, DateTime, DateTime] 激活
提交过时条件
00:00:00 --> inserted: Person[personLastDatas=<null>]
00:00:00 --> 触发所有规则
00:00:00 <-- '插入过时条件' 已被元组 [Person] 激活
提交过时条件

函数源代码

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;

规则 "插入过时条件"
        优先级 -1
        Person(personLastDatas == null)
        $person : Person()
        并且 $tooOldInstant : DateTime() from now.minusDays(10)
        并且 DateTime( this < $tooOldInstant ) from accumulate (
            LastData( $date : lastDate ) from $person.personLastDatas,
            maxValue($date)
        )
        System.out.println("提交过时条件");
end

请注意,代码中的注释已经被移除。如果您需要保留注释,请在翻译后添加。

英文:

Option 1

rule &quot;Insert a too old condition&quot;
salience -1
when
Person(personLastDatas == null)
or
$person : Person()
and $tooOldInstant : DateTime() from now.minusDays(10)
and DateTime( this &lt; $tooOldInstant ) from accumulate (
LastData( $date : lastDate ) from $person.personLastDatas,
maxValue($date)
)
then
System.out.println(&quot;submitTooOldCondition&quot;);
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&lt;DateTime&gt; 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 = &quot;classpath:/test.drl&quot;)
public class PlaygroundTest {
@Rule
public DroolsAssert drools = new DroolsAssert();
@Test
@TestRules(expectedCount = { &quot;2&quot;, &quot;Insert a too old condition&quot; })
public void testIt() {
drools.setGlobal(&quot;now&quot;, 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 --&gt; inserted: Person[personLastDatas=[org.droolsassert.LastData@25243bc1, org.droolsassert.LastData@1e287667]]
00:00:00 --&gt; fireAllRules
00:00:00 --&gt; inserted: Person[personLastDatas=[org.droolsassert.LastData@76f10035, org.droolsassert.LastData@5ab9b447]]
00:00:00 --&gt; fireAllRules
00:00:00 &lt;-- &#39;Insert a too old condition&#39; has been activated by the tuple [Person, DateTime, DateTime]
submitTooOldCondition
00:00:00 --&gt; inserted: Person[personLastDatas=&lt;null&gt;]
00:00:00 --&gt; fireAllRules
00:00:00 &lt;-- &#39;Insert a too old condition&#39; has been activated by the tuple [Person]
submitTooOldCondition

function source

public class MaxValueAccumalateFunction implements AccumulateFunction&lt;HashSet&lt;DateTime&gt;&gt; {
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
@Override
public HashSet&lt;DateTime&gt; createContext() {
return new HashSet&lt;&gt;();
}
@Override
public void init(HashSet&lt;DateTime&gt; context) throws Exception {
}
@Override
public void accumulate(HashSet&lt;DateTime&gt; context, Object value) {
if (context.isEmpty() || context.iterator().next().isBefore((DateTime) value)) {
context.clear();
context.add((DateTime) value);
}
}
@Override
public void reverse(HashSet&lt;DateTime&gt; context, Object value) throws Exception {
}
@Override
public Object getResult(HashSet&lt;DateTime&gt; context) throws Exception {
return context.isEmpty() ? new DateTime(0) /*null*/ : context.iterator().next();
}
@Override
public boolean supportsReverse() {
return false;
}
@Override
public Class&lt;?&gt; getResultType() {
return null;
}
}

rule source

import org.joda.time.DateTime;
import accumulate org.droolsassert.MaxValueAccumalateFunction maxValue;
global DateTime now;
rule &quot;Insert a too old condition&quot;
salience -1
when
Person(personLastDatas == null)
or
$person : Person()
and $tooOldInstant : DateTime() from now.minusDays(10)
and DateTime( this &lt; $tooOldInstant ) from accumulate (
LastData( $date : lastDate ) from $person.personLastDatas,
maxValue($date)
)
then
System.out.println(&quot;submitTooOldCondition&quot;);
end

huangapple
  • 本文由 发表于 2020年5月19日 20:16:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/61890851.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定