JAVA规则引擎总结

第一章 规则引擎初步了解

1 为什么会有规则引擎?

背景:复杂企业级项目的开发以及其中随外部条件不断变化的业务规则(business logic),迫切需要分离商业决策者的商业决策逻辑和应用开发者的技术决策,并把这些商业决策放在中心数据库或其他统一的地方,让它们能在运行时(即商务时间)可以动态地管理和修改从而提供软件系统的柔性和适应性。规则引擎正是应用于上述动态环境中的一种解决方法。

企业管理者对企业级IT系统的开发有着如下的要求:

为提高效率,管理流程必须自动化,即使现代商业规则异常复杂;

市场要求业务规则经常变化,IT系统必须依据业务规则的变化快速、低成本的更新;

为了快速、低成本的更新,业务人员应能直接管理IT系统中的规则,不需要程序开发人员参与。

2 什么是规则引擎?

也许这又是一种“先有蛋还是先有鸡”哲学争论,在JSR-94种也几乎没有定义,规则引擎这个术语是非常不明确的,因为任何以任意形式使用能够应用于数据生成结果的规则的系统都可以称为规则引擎。包括像表单验证和动态表达式引擎这样的简单系统都可以称之为规则引擎。可以这样理解规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据规则做出业务决策。

3 为何要使用规则引擎?

3.1 声明式编程

规则引擎允许你描述做什么而不是如何去做。

这里的主要优点是使用规则更加容易对复杂的问题进行表述,并得到验证。 (规则比编码更容易阅读).

规则系统能够解决非常非常困难的问题,并提供了方案怎样达到和在解决问题的方向上所作的每一个决定的原因(这对于类似神经网络这样的AI系统来说不容易达到)

3.2逻辑与数据分离

数据保存在系统对象中,逻辑保存在规则中。这根本性的打破了面向对象系统中将数据和逻辑耦合起来的局面,这点是有利的也是不利的,在于你的观察角度。这样做的结果是,将来逻辑发生改变时更容易被维护,因为逻辑保存在规则中。这点在逻辑是跨领域或多领域中使用时尤其有用。通过将逻辑集中在一个或数个清晰的规则文件中,取代了之前分散在代码中的局面。

3.3 速度及可测量性

Rete算法、Leaps算法,以及由此衍生出来的Drools的Rete、Leaps算法,提供了对系统数据对象非常有效率的匹配。这些都是高效率尤其当你的数据是不完全的改变(规则引擎能够记得之前的匹配)。这些算法经过了大量实际考验的证明。

3.4 知识集中化

通过使用规则,将建立一个可执行的规则库。这意味着规则库代表着现实中的业务策略的唯一对应,理想情况下可读性高的规则还可以被当作文档使用。

3.5 工具集成

例如Eclipse(将来可能在基于Web的界面上)这样的工具为规则的修改与管理、即时获得反馈、内容验证与修补提供了办法。审查与调试工具同样也可用了。

3.6 解释机制

通过将规则引擎的决断与决断的原因一起记录下来,规则系统提供了很好的“解释机制”。

3.7易懂的规则

通过建立对象模型以及DSL(域定义语言),你可以用接近自然语言的方式来编写规则。这让非技术人员与领域专家可以用他们自己的逻辑来理解规则(因为程序的迷宫已经被隐藏起来了) 。

4 何时应当使用规则引擎?

最简短的回答就是“当没有令人满意的传统的程序设计方法能够解决这个问题时”。

下面对这个所谓的传统解决方法的一个描述:

对于传统代码来说,问题需要的精确度太高。

这种问题可能并不复杂,但是你找不到一种稳定的方法去建立它。

问题超越了任何有明显运算法则的方案。

它是一个难以解决的复杂问题,没有明显的传统解决方案或者问题没有一个准确的定论。

业务逻辑经常发生改变

逻辑本身是简单的(但不是指过于简单),但是规则经常发生变化。在许多软件组织中正式版本的间隔是较长并且较少的,规则可以在适当的安全前提下帮助提供一定的敏捷性。

领域专家(或者业务分析师)是非技术人员

领域专家通常对业务规则和流程具有很好的认知。他们通常是不了解软件技术的人员,但是具有很好的逻辑性。规则能够让他们用自己的术语来描述业务逻辑。当然他们仍然需要严密的思考和良好的逻辑思维能力(许多在非软件技术型岗位上的人没有进行过形式逻辑的训练,因此在和他们工作时要特别小心,在将业务知识编撰成规则时,要特别注意业务规则和流程应当是当前能够理解的)。

 

5 如何使用规则引擎?

由于规则引擎是软件组件,所以只有开发人员才能够通过程序接口的方式来使用和控制它,规则引擎的程序接口至少包含以下几种API:

加载和卸载规则集的API;

数据操作的API;

引擎执行的API。

开发人员在程序中使用规则引擎基本遵循以下5个典型的步骤:

创建规则引擎对象;

向引擎中加载规则集或更换规则集;

向引擎提交需要被规则集处理的数据对象集合;

命令引擎执行;

导出引擎执行结果,从引擎中撤出处理过的数据。

使用了规则引擎之后,许多涉及业务逻辑的程序代码基本被这五个典型步骤所取代。

一个开放的业务规则引擎应该可以“嵌入”在应用程序的任何位置,不同位置的规则引擎可以使用不同的规则集,用于处理不同的数据对象。此外,对使用引擎的数量没有限制。

6 何时不要使用规则引擎 ?

因为规则引擎是动态的 (动态的在这里意味着规则可以象数据一样保存、管理和更新),它们通常被看作发布软件系统的一种解决方案(大多数IT部门似乎存在的目的是防止软件系统被遗弃)。如果这是你希望使用规则引擎的原因,应当意识到在可以写出公开发布的规则时,规则引擎能够以最佳方式工作。做为另一个方面,你也可以考虑使用数据驱动的设计(查找表)或者脚本/流程引擎带有能够在数据库中管理并能够动态更新的脚本。对特定的工作要使用恰当的工具。当然,必要时老虎钳可以当作锤子用,但那并不是发明老虎钳的本意。”

7 规则引擎的架构和推理

规则引擎的架构如下图所示:

规则引擎的推理步骤如下:

a.将初始数据(fact)输入至工作内存(Working Memory)。

b.使用Pattern Matcher将规则库(Rules repository)中的规则(rule)和数据(fact)比较。

c.如果执行规则存在冲突(conflict),即同时激活了多个规则,将冲突的规则放入冲突集合。

d.解决冲突,将激活的规则按顺序放入Agenda。

e.执行Agenda中的规则。重复步骤b至e,直到执行完毕Agenda中的所有规则。

任何一个规则引擎都需要很好地解决规则的推理机制和规则条件匹配的效率问题。

当引擎执行时,会根据规则执行队列中的优先顺序逐条执行规则执行实例,由于规则的执行部分可能会改变工作区的数据对象,从而会使队列中的某些规则执行实例因为条件改变而失效,必须从队列中撤销,也可能会激活原来不满足条件的规则,生成新的规则执行实例进入队列。于是就产生了一种“动态”的规则执行链,形成规则的推理机制。这种规则的“链式”反应完全是由工作区中的数据驱动的。

规则条件匹配的效率决定了引擎的性能,引擎需要迅速测试工作区中的数据对象,从加载的规则集中发现符合条件的规则,生成规则执行实例。1982年美国卡耐基·梅隆大学的Charles L. Forgy发明了一种叫Rete的算法,很好地解决了这方面的问题。目前世界顶尖的商用业务规则引擎产品基本上都使用Rete算法。

8规则引擎的算法

大部分规则引擎产品的算法,基本上都来自于Dr. Charles Forgy在1979年提出的RETE算法及其变体,Rete算法是目前效率最高的一个Forward-Chaining推理算法,Drools项目是Rete算法的一个面向对象的Java实现,Rete算法其核心思想是将分离的匹配项根据内容动态构造匹配树,以达到显著降低计算量的效果。

详情请见CIS587:The RETE Algorithm,The Rete Algorithm,RETE演算法,《专家系统原理与编程》中第11章等。

9 Java规则引擎商业产品

组织/厂商名称主页说明JSR 兼容性
ILOGJRuleshttp://www-2000.ibm.com/software/cn/websphere/ilog/products/jrules/index.html已被IBM收购JRules 4.6
DroolsDroolshttp://www.jboss.org/drools.html已被Redhat JBoss收购,转向商业化,仍有社区版本基于JSR94和Rete算法
Sandia LabsJESShttp://herzberg.ca.sandia.gov/jess/JESS 6.1 p6
JLisaJLisbhttp://jlisa.sourceforge.net/商业化/社区支持
TmaxSoftProRulehttp://cn.tmaxsoft.com/jsp/main.jsp
Fair Isaac, Blaze AdvisorBlaze Advisorhttp://www.fairisaac.com/fic/cn/fic/cn/index.htm
PegaSystems Inc.PegaRULES Process Commanderhttp://www.pega.com/CRM行业的解决方案供应商,转向BPM领域

第二章. Drools 规则引擎

2.1. 概述

Drools被分为两个主要的部分:编制和运行时

编制的过程包括为规则建立DRL或XML文件,传入一个由Antlr 3文法器定义的解析器中。[下面的翻译要参考图1.10理解]解析器对文件中规则文法的正确性进行检查并为descr建立一个中间结构,在AST中的descr代表规则的描述。AST然后将descr传入Package Builder中,由其进行打包。Package Builder同时负责包括打包中用到的所有代码产生器和编译器。

Package对象是自包含并可配置的,它是一个包含规则的序列化的对象。

Figure 2.10. 编制组件

RuleBase是运行时组件,包含一个或多个Package。Package在任何时候都可以向RuleBase中添加或删除。

一个RuleBase可以同时初始化多个Working Memory,在其间维护着一个弱引用,除非重新进行配置。Working Memory包含许多子组件,如Working Memory Event Support(事件支持), Truth Maintenance System(真值维护系统), Agenda 和 Agenda Event Support(事件支持)。向Working Memory中设置对象的工作可能要在建立了一个或多个激活的规则后才结束。Agenda负有规划激活规则运行的责任。

Figure 2.11. 运行时组件

2.2. 编制

Figure 2.12. PackageBuilder

DrlParser, XmlParser 和 PackageBuilder这三个类被用来进行编制。两个分析类用来从提供的可读流实例中建立descr AST模型。PackageBuilder提供相应API使得你可以不用记住这些类。"addPackageFromDrl" 和 "addPackageFromXml"是两个常用方法——都要求一个可读流实例作参数。下面的例子说明一个如何建立一个包括XML和DRL规则文件的包,文件的位置在相对于classpath变量中定义的路径下。注意,对于增加到当前PackageBuilder实例中的所有规则包,必须具有同样的包名称空间。

Example 2.1. 从多个源中建立包

PackageBuilder builder = new PackageBuilder();

builder.addPackageFromDrl( new InputStreamReader( getClass().getResourceAsStream( "package1.drl" ) ) );

builder.addPackageFromXml( new InputStreamReader( getClass().getResourceAsStream( "package2.xml" ) ) );

builder.addRuleFlow( new InputStreamReader( getClass().getResourceAsStream( "ruleflow.rfm" ) ) );

Package pkg = builder.getPackage();

有一个要点,在正式使用PackageBuilder之前,要先检查其中是否有错误。当一个被打断的Package加入ruleBase时,将会抛出一个InvalidRulePackage,错误被包含在这个Package并且只有一个等价于toString()的方法有效。如果你询问PackageBuilder,可以返回更多的有效信息。

Example 2.2. 使用 PackageBuilder 检查错误

PackageBuilder builder = new PackageBuilder();

builder.addPackageFromDrl( new InputStreamReader( getClass().getResourceAsStream( "package1.drl" ) ) );

PackageBuilderErrors errors = builder.getErrors();

PackagBuilder使用PackageBuilderConfiguration类配置。

Figure 2.13. PackageBuilderConfiguration

PackageBuilderConfiguration的默认值可以通过程序设置或通过外部特性配置在一开始就改变。在配置之前先通过ChainedProperties类在许多位置搜索drools.packagebuilder.conf文件;当发现它们之后,加入其中定义的配置到主要特性列表中;这提供了一个搜索的级别优先性。位置优先性的顺序是:系统特性,用户在系统特性中定义的文件,用户Home目录,工作目录,各种META-INF位置。除此之外,droosl-compiler jar在它的META-INF目录中有它自己的默认设置。

当前PackageBulderConfiguration处理Accumulate函数的注册,语言的注册以及主要的类调用器(ClassLoader)。

Drools有一个可插入的语言系统,它允许其它语言编译和执行表达式与代码块,当前两种支持的语言是Java和MVEL。每一个都有它自己的DialectConfiguration接口实现;Javadocs文档中提供了每一个setter/getter的细节以及用于配置它们的特性名称。

Figure 2.14. JavaDialectConfiguration

JavaDialectConfiguration允许设置用于支持它的编译器和语言水平。你可以通过在可以被ChainedProperties 实例发现的packagebuilder.conf文件中设置"drools.dialect.java.compiler"特性来覆盖它,或者你可以通过下面的程序在运行时改变。

Example 2.3. 配置JavaDialectConfiguration 使用JANINO

PackageBuilderConfiguration cfg = new PackageBuilderConfiguration( );

JavaDialectConfiguration javaConf = (JavaDialectConfiguration) cfg.getDialectConfiguration( "java" );

javaConf.setCompiler( JavaDialectConfiguration.JANINO );

如果在你的classpath中没有Eclipse JDT Core,你必须在实例化PackageBuilder之前覆盖这个默认的编译器设置,你可以通过一个ChainedProperties类可以发现的packagebuilder特性文件或通过下面的程序方式改变它;注意我使用特性为启动注入值的时间。

Example 2.4. 配置 JavaDialectConfiguration 使用 JANINO

Properties properties = new Properties();

properties.setProperty( "drools.dialect.java.compiler",

"JANINO" );

PackageBuilderConfiguration cfg = new PackageBuilderConfiguration( properties );

JavaDialectConfiguration javaConf = (JavaDialectConfiguration) cfg.getDialectConfiguration( "java" );

assertEquals( JavaDialectConfiguration.JANINO,

javaConf.getCompiler() ); // 确认编译器被正确配置

当前允许在Janino和Eclipse JDT两个编译器之间选择一个,以及设置不同的JDK源码级别("1.4" 和"1.5")和一个父类装载器。默认的编译器是在JDK1.4水平上的Eclipse JDT Core编译器,以及将设置到"Thread.currentThread().getContextClassLoader()"中的父类装载器。

接下来的代码显示如何指定JANINO编译器:

Example 2.5. 通过特性配置PackageBuilder使用JANINO

PackageBuilderConfiguration conf = new PackageBuilderConfiguration();

conf.setCompiler( PackageBuilderConfiguration.JANINO );

PackageBuilder builder = new PackageBuilder( conf );

MVELDialectConfiguration更加简单,并且只允许精确的(strict)的模式被打开或关闭,默认strict是true;这意味着所有方法调用必须是类型安全的,不管是经过推断声明或显示声明的类型。

Figure 2.15. MvelDialectConfiguration

2.3. RuleBase

Figure 2.16. RuleBaseFactory

RuleBase使用RuleBaseFactory实例化,默认情况下返回一个ReteOO的RuleBase。Package通过使用addPackage方法按顺序加入。你可以指定任何名称空间的Packages或者同一名称的多个包加入RuleBase。

Example 2.6. 增加Package到新的RuleBase

RuleBase ruleBase = RuleBaseFactory.newRuleBase();

ruleBase.addPackage( pkg );

Figure 2.17. RuleBase

RuleBase包含一个或多个规则包,它们已经被校验和编译完成,以备使用。RuleBase是可序列化的,因此它可以被配置到JNDI或其它类似的服务上。典型的,RuleBase在第一次使用时产生并缓存,这是一个昂贵的操作(花费大量时间)。RuleBase通过RuleBaseFactory来初始化,默认情况下返回一个ReteOO RuleBase(面向对象化的Rete算法的RuleBase)。可以指定参数决定返回ReteOO还是Leaps。接着使用addPackage方法将包加入。你可以指定在任意名称空间中的包,并且处于相同名称空间的多个包可以被加入。

RuleBase的实例是线程安全的,这意味着你可以在你的应用间共享一个实例,现实中可能是一个Web应用程序。在RuleBase上最常见的操作是建立一个新的规则Session,可以是有状态或无状态的。

RuleBase也保存着任何由它产生的有状态Session的引用,因此如果规则改变(被新增/删除等等。对于长时间运行的Session),它们可以用最新的规则更新而不需要重启Session。你可以指定不去维护一个引用,但只在你明确知道Rule Base不会被更新的情况下使用。RuleBase不会对无状态Session保持引用。

ruleBase.newStatefulSession(); // maintains a reference.

ruleBase.newStatefulSession( false ); // do not maintain a reference

Package可以在任何时候新增或删除——所有的变更都会传播到现存的有状态Session中;不要忘记调用fireAllRules()激发已经激活的规则。

ruleBase.addPackage( pkg ); // 新增package实例

ruleBase.removePackage( "org.com.sample" ); //通过名称空间删除package和所有部件

ruleBase.removeRule( "org.com.sample", "my rule" ); // 从名称空间删除指定规则

有方法可以用来删除单独的规则,但是没有方法新增一个单独的规则——要实现这个目标,增加一个只含有一个规则的Package。

RuleBaseConfigurator可以被用来指定RuleBase的附加行为。RuleBaseConfigurator在加入Rule Base中之后被设置为不可改变。几乎引擎的所有优化方式都可以在这里打开或关闭,也可以设置要执行的行为。用户通常关心插入行为(同一性和等同性)以及叉集行为(删除或保持同一性相等的叉集)。

RuleBaseConfiguration conf = new RuleBaseConfiguration();

conf.setAssertBehaviour( AssertBehaviour.IDENTITY );

conf.setRemoveIdentities( true );

RuleBase ruleBase = RuleBaseFactory.newRuleBase( conf );

Figure 2.18. RuleBaseConfiguration

2.4. WorkingMemory 和有状态/无状态Sessions

Figure 2.19. WorkingMemory

WorkingMemory保存所有插入的数据的引用直到该数据被删除,并且它是与你的应用程序发生交互的地方。WorkingMemory是有状态的对象,它们可以短期或长期存在。

2.4.1. Facts

Facts是由你的应用程序设置到Working Memory 中的对象。Facts可以是任何规则可以存取的Java对象。规则引擎完全不会克隆对象,它仅仅是保存对对象的一个引用/指针。Facts是你的应用程序的数据。字符串以及其它没有getter和setter方法的类不是有效的Facts,不能被用于字段约束,因为字段约束依赖于根据JavaBean标准的getter和setter方法来与对象交互。

2.4.2. Insertion

" Insert "是通知working memory关于facts的操作。例如,WorkingMemory. insert (yourObject)。当你插入一个fact时,它检查与规则的匹配情况。这意味着所有工作在插入Fact期间就已经完成,但在你调用"fireAllRules()"方法之前没有任何规则被执行。在你完成所有fact的设置之前你不应该调用"fireAllRules()"。人们通常会误会所有的相关工作在"fireAllRules()"调用后才进行。专家系统通常使用术语"assert"或"assertion"来指代将fact设置到系统中的操作,但是随着assert在大多数语言中成为了一个关键字,我们使用Insert关键字来代替它,以避免关键字冲突。

当对象被Insert到WorkingMemory中时,它返回一个FactHandle。这个FactHandle是象征着你插入WorkingMemory中的对象,它也是当你希望从Working Memory中删除或修改一个对象时所必须的。

Cheese stilton = new Cheese("stilton");

FactHandle stiltonHandle = session.insert( stilton );

在RuleBase中Working Memory在插入时可能有两种操作方式——等同性equality和同一性identity,identity是默认操作。

Identity意味着Working Memory使用一个IdentityHashMap去保存所有被插入进来的对象。被插入进来的新的实例都会返回一个新的FactHandle。如果一个实例被插入进来两次,将返回前一个的FactHandle,对相同实例直接忽略第二次插入。

Equality意味着Working Memory使用一个HashMap保存所有插入的对象。这个模式下,当插入的 Object 相等时,它返回同一个 FactHandle。

2.4.4.3. Retraction

当你 retract 一个 fact , WorkingMemory 将不再跟踪那个 fact 。任何被 activated 并依赖那个 fact 的规则将被取消。注意:完全有可能存在某条规则是依赖于一个 fact 的“不存在”( non existence )。在这种情况下, retract 一个 fact 将导致一条规则被激活。对一个 Fact 进行 Retraction ,必须用 Insert时返回的那个 FactHandle 做为参数。

Cheese stilton = new Cheese("stilton");

FactHandle stiltonHandle = session.insert( stilton );

....

session.retract( stiltonHandle );

2.4.4. Update

当一个 Fact 被修改了,会通知规则引擎进行重新处理。在规则引擎内部实际上是对旧的 Fact 进行 retract ,然后对新的 Object 再进行 insert。要使用 modifyObject() 方法来通知 Working Memory ,被改变的 Object 并不会自己通知规则引擎。注意: modifyObject() 方法总是要把被修改的 Object 做为第二参数,这就允许你把一个不可变对象替换为另一个新对象。update()方法只能在影子代理打开的情况下对对象使用。如果你没有使用影子代理,那你必须在调用update之前先session. modifyRestract (),然后在update之后调用session.modifyInsert()。

Cheese stilton = new Cheese("stilton");

FactHandle stiltonHandle = workingMemory.insert( stilton );

....

stilton.setPrice( 100 );

workingMemory.update( stiltonHandle, stilton );

2.4.5. 全局变量

Global 是一个能够被传进 WorkingMemory 但不需要进行insert操作的命名对象。大多数这些对象被用来作为静态信息或被用在一条规则的RHS 部分的服务,或者可能是从规则引擎返回对象的一种方法。 如果你在规则的LHS部分使用全局变量,那必须确保它不会改变。全局变量在Session中设置之前必须首先在Drl文件中定义。

global java.util.List list

随着Rule Base现在知道全局变量的定义和它的类型,任何Session现在可以调用session.setGlobal;如果之前对全局变量的类型和定义有错误,将会抛出异常。使用session.setGlobal(identifier, value)将全局变量设置到Session中;

List list = new ArrayList();

session.setGlobal("list", list);

 

如果一条规则在你 setGlobal 之前调用了定义的 Global ,会抛出一个 NullPointerException。

2.4.6. 影子(shadow)Facts

影子Fact是一个对象的浅拷贝,用来缓存被设置到working memory 中的对象的拷贝。术语shadow facts来自JESS(Java Expert System Shell)的一个特性。

影子Fact的用意是回溯真值维护的概念。基本思路是,专家系统应当确保推导出的结论是准确的。一个运行中的应用系统可能在规则引擎的推导过程中改变了fact的数据。当发生这种情况时,规则引擎必须知道这种改变发生了,并进行适当的处理。通常有两种方法来保证真实性。第一种是在推论期间锁定所有的Facts。第二种是建立一个对象的缓存,并且强迫所有的改变必须通过规则引擎进行。在这种方法下,改变能够有序的进行。Shadow facts在多线程的环境中是非常重要的,当一个规则引擎被多个Session共享时。缺少了真值维护,系统无法证明结论是正确的。Shadow facts带来的主要优点是它使得开发更加容易。当需要开发者强制保持对fact修改的跟踪时,是非常容易出错,并且很难调试的。在没有增加对fact的更改进行跟踪的功能时,使用规则引擎建立一个有一定复杂度的系统是非常困难的。

Drools4.0对影子Fact有完全的支持,使用完全透明的lazy代理实现这一机制。影子fact机制默认是启用的,不会从外部的代码中看出来,甚至是在规则代码块中的代码。

虽然影子fact是确保引擎完整性的一个很好的方法,但是它们也给推论过程增加了一些负载。因此Drools4.0支持对它们更细致的控制,能够对单独的每一个类启用/禁用它们。要在所有的类上禁用影子fact,在系统特性的配置中设置以下特性:

drools.shadowProxy = false

另外,它也可以通过一个API调用来禁用它:

RuleBaseConfiguration conf = new RuleBaseConfiguration();

conf.setShadowProxy( false );

...

RuleBase ruleBase = RuleBaseFactory.newRuleBase( conf );

只对列表中的类禁用影子代理,使用下面的特性:

drools.shadowproxy.exclude = org.domainy.* org.domainx.ClassZ

如上所示,使用空格来分隔多于一个类的列表,并且使用'*'来作为广义字符。

注意: 对一个类禁用影子fact,意味着解除了引擎对类属性发生变化时的跟踪机制。它意味着一旦fact杯插入引擎后,不能有任何改变,否则会带来不可预期的行为。即使使用update()都没有帮助。在影子机制被禁止时,唯一安全的修改fact属性的方法是在属性改变前调用modifyRetract(),改变后调用modifyAssert()。

2.4.7. 属性更改监听器(Property Change Listener)

如果你的 fact 对象是 JavaBean ,你可以为它们实现一个 property change listener ,然后把它告诉规则引擎。这意味着,当一个 fact 改变时,规则引擎将会自动知道,并进行响应的动作(你不需要调用 modifyObject() 方法来通知 WorkingMemory )。代理库将会帮助实现这一切。要让 Property Change Listener 生效,还要将 fact 设置为动态( dynamic )模式,通过将 true 做为 assertObject() 方法的第二个参数来实现:

Cheese stilton = new Cheese("stilton");

//specifies that this is a dynamic fact

FactHandle stiltonHandle = workingMemory.insert( stilton, true );

然后要在 JavaBean 中加入一个 PropertyChangeSupport 实例,和两个方法: addPropertyChangeListener() 和 removePropertyChangeListener() 。最后要在 JavaBean 的 setter 方法中通知 PropertyChangeSupport 所发生的变化。示例代码如下:

private final PropertyChangeSupport changes = new PropertyChangeSupport( this );

...

public void addPropertyChangeListener(final PropertyChangeListener l) {

this.changes.addPropertyChangeListener( l);

}

public void removePropertyChangeListener(final PropertyChangeListener l) {

this.changes.removePropertyChangeListener( l );

}

...

public void setState(final String newState) {

String oldState = this.state;

this.state = newState;

this.changes.firePropertyChange( "state",

oldState,

newState );

}

2.4.8. Initial Fact

为了支持像not这样的条件元素,需要让引擎获得一个Initial Fact的种子。这个fact有着特定作用,不会被用户访问到。

在一个新的Working Memory最初的动作(insert, fireAllRules)中,这个Initial Fact将通过Rete网络传播。这允许规则没有LHS,或者不使用常规的fact(例如规则利用from从外部将数据拉过来)。例如,假设一个新的Working Memory已经建立,并且还没有任何Fact被设置,调用fireAllRules将导致Initial Fact的传播,并可能激活规则(否则,在没有其它fact的情况下不会发生任何事情)。

2.5. StatefulSession

Figure 2.20. StatefulSession

StatefulSession继承了WorkingMemory类,它只是简单的加上了异步方法和一个dispose()方法。

Example 2.7. 创建StatefulSession

StatefulSession session = ruleBase.newStatefulSession();

2.6. StatelessSession

Figure 2.21. StatelessSession

StatelessSession包容了WorkingMemory,而不是继承它,它的主要焦点是在决策服务类型的场景(decision service type scenarios)上。

Example 2.8. 创建StatelessSession

StatelessSession session = ruleBase.newStatelessSession();

Api因为问题域的原因而减少,并且变得更简单;这也意味着维护这些服务更加简单。RuleBase从不维护一个指向StatelessSession的引用,因此dispose()是不需要的,它只有一个execute()方法来获取一个对象或一组对象,没有insert或fireAllRules方法。Execute方法遍历所有插入的对象,并在最后调用fireAllRules()方法;Session完成。可以使用executeWithResults方法返回需要查询的Session的结果信息,方法返回一个StatelessSessionResult对象。原因是在远程环境下,你并不总是需要一个返回的负载(返回消息所消耗的网络时间等),因此返回是可选的。

setAgendaFilter, setGlobal 和setGlobalResolver在整个Session期间共享它们的状态,因此每个execute()调用将使用设置好的AgendaFilter,或者查看是否有任何之前设置的全局变量等等。

StatelessSession当前不支持propertyChangeLissteners。Excute方法的异步执行版本被支持,记得在特定被管理的线程环境中(如JEE)覆盖ExecutorService实现。

ExecutorServic也支持顺序模型,这是一个特别的优化模式,将使用更少的内存和更快的执行速度;请在Sequential章节查看详细说明。

2.7. Agenda

Figure 2.22. 两阶段执行

Agenda 是 RETE 的一个特点。在一个 WorkingMemory 操作发生时,可能会有多条规则发生完全匹配。当一条规则完全匹配的时候,一个 Activation 就被创建(引用了这条规则和与其匹配的 facts ),然后放进 Agenda 中。 Agenda 通过使用冲突解决策略( Conflict Resolution Strategy )来安排这些 Activations 的执行。

引擎工作在一个“ 2 阶段”模式下:

1)  WorkingMemory Actions : assert 新的 facts ,修改存在的 facts 和 retract facts 都是 WorkingMemory Actions 。通过在应用程序中调用 fireAllRules() 方法,会使引擎转换到 Agenda Evaluatioin 阶段。

2)  Agenda Evaluation :尝试选择一条规则进行激发( fire )。如果规则没有找到就退出,否则它就尝试激发这条规则,然后转换到 WorkingMemory Actions 阶段,直到 Agenda 中为空。

Figure 2.23. 两阶段执行

这个过程一直重复,直到 Agenda 是空的,此时控制权就回到应用程序中。。当 WorkingMemory Actions 被发生时,没有规则被激发。

2.7.1. Conflict Resultion

当有多条 rules 在 agenda 中,就需要解决冲突。当激发一条规则时,会对 WorkingMemory 产生副作用。规则引擎需要知道规则要以什么顺序来激发(例如,激发 rule A 可能会引起 rule B 被从 agenda 中移除。)

Drools 采取的冲突解决策略有 2 种,按照优先级排列如下: Salience , LIFO (后进先出)。最易懂的策略是“ Salience ”,即优先级, 用户可以为某个 rule 指定一个高一点的优先级(通过附给它一个比较大的数字)。高 Salience 的 rule 将会被优先激发。LIFO 的优先级基于分配给Working Memory 操作(Action)的计数值, 从同样的Action建立的多个规则具有相同的值,相同值的规则执行顺序被看作可任意进行。

作为一个约定,不要将规则考虑为按照特定的顺序执行是一个好习惯,这样在编辑规则时不用考虑流的顺序。

可以通过调用RuleBaseConfiguration.setConflictResolver()方法来设置自定义的冲突解决机制,或者使用特性"drools.conflictResolver"。

2.7.2. Agenda Groups

Agenda Groups 是划分 Agenda 中 rules (其实是“ activations ”)的一种方法。在任意一个时刻,只有一个 group 拥有“ focus ”,这意味着只有在那个 group 中的 activations 才是有效的。

它们有时在CLIPS术语中被称为模块"modules" 。Agenda Groups 是在 grouped rules 之间创建一个“流”( flow )的简便的方法。你可以在规则引擎中,或是用 API 来切换具有焦点的组。如果你的规则有很明确的多“阶段”( phases )或多“序列”( sequences )的处理,可以考虑用 Agenda Groups 来达到这个目的。

每次调用 setFocus() 方法的时候,那个 Agenda Group 就会被压入一个堆栈,当这个有焦点的组为空时,它就会被弹出,然后下一个组就会被执行。一个 Agenda Group 可以出现在堆栈的多个位置。默认的 Agenda Group 是“ MAIN ”,所有没有被指定 Agenda Group 的 Activations 都被放到那个组中,这个组总是被放在堆栈的第一个组,并默认给予焦点。

2.7.3. Agenda Filters

Figure 2.24. AgendaFilters

过滤器是过滤器接口的可选实现,被用来允许/阻止一个激活的规则被激发(哪一个过滤器被使用完全依赖于执行)。Drools提供下面的已经默认实现的过滤器。

RuleNameEndWithAgendaFilter

RuleNameEqualsAgendaFilter

RuleNameStartsWithAgendaFilter

RuleNameMatchesAgendaFilter

要使用一个 filter 就要在调用 fireAllRules() 方法的时候指定它。下面的例子将对所有名字以“ Test ”结尾的规则进行过滤:

workingMemory.fireAllRules( new RuleNameEndsWithAgendaFilter( "Test" ) );

2.8. Truth Maintenance with Logical Objects

通常在将一个Fact插入到Working Memory后,需要显式的删除该对象。但使用逻辑插入(logical assertions),当最初插入该对象的条件已经不再为真时,Fact将被自动删除。实际上的实现比这个更灵活!如果已经没有条件可以支持这个logical assertion时,它才会被自动删除。

通常的插入(相对逻辑插入)被称为被设定的(Stated),例如Fact已经被设定了(Stated),就像直觉概念一样。通过使用HashMap和一个计数器,我们跟踪Fact已经被多少个等式所设定。当我们逻辑插入一个对象时,我们被告知引用它并且它被激活的规则所引用。对每一个逻辑设定,相同的对象只能有一个,每一个后来的相同对象的逻辑插入会增加一个引用计数。当所有的引用被移出后,这个被逻辑插入的对象自动删除。

如果在Working Memory中已经有一个相同的被设定的(Stated)对象,再对该对象进行逻辑插入将会失败并返回null。当Working Memory中已经有一个逻辑插入的对象,再进行普通插入时,将覆盖这个对象,如何覆盖依赖于在配置文件中定义的"WM_BEHAVIOR_PRESERVE"参数。当参数被插入为抛弃时,我们使用对象当前的句柄进行更换新对象的操作,旧的对象被替换掉——这是默认的行为,除非我们尝试替换设定的(Stated)对象,这时将建立一个新的句柄。

之前的描述可能让你觉得迷惑,希望下面的流程图可以给予足够的帮助。当规则引擎返回了一个新的对象句柄时,也意味着该对象已经完成在网络中的传播。

Figure 2.25. Stated Insertion

Figure 2.26. Logical Insertion

2.8.1. 示例

用一个例子可能可以让你更清楚一些。假设有一个信用卡处理的应用,用来处理一个给定的帐户,关于对单个帐户的处理我们有一个working memory保存了处理规则。规则引擎尽量去判断信用卡事务是否是欺骗性的。比如说RuleBase中有规则用来判断何时属于欺诈情况,何时是正常情况。

当然其中也有很多规则只是执行标准的计算操作,并不去判断欺骗性。有很多情况可以被认为信用卡业务具有欺骗性,例如被银行通告的某些人,大量的交易,在地理上完全不同的国家发生交易,被盗信用卡等等。与其将所有的判断条件分散到许多规则里,不如假设有一个Fact类叫"SuspiciousAccount"。

这样一系列规则的工作就是去检查可能引起欺骗风险升高的因素,如果它们被激发了,简单的设置一个SuspiciousAccount对象到Working Memory中,这样其它关注欺骗性的规则只要检查是否存在SuspiciousAccount对象就可以了。这样的优点是可以有很多规则对欺骗性进行判断,但不需要依赖其它规则的处理。当SuspiciousAccount对象被移除后,规则引擎返回到正常的操作状态,例如not SuspiciousAccount条件可以激发之前很多被阻塞的规则。

如果你仔细的观察,你可能会注意到对真值的维护,好象逻辑设置这样,使得规则的行为更贴近于真实情况,使得规则更容易被管理。

2.8.2. 重要提示: Java对象的等同性

需要注意的是,为了实现真值维护的正确工作,你的Fact对象(大多是Javabean)需要正确的重载equals和hashCode方法。作为真值维护系统,需要知道两个不同的物理对象在值上是否相等,在Java标准中这需要对象的equals方法和hashCode方法都要正确重栽。

只有在equals返回true并且hashCode方法返回相同的hash值时,才能认为两个对象相等。可察看Java API获取更多的细节,但是需要牢记的是equals方法和hashCode方法都要正确重栽。

2.9. 事件模型(Event Model)

事件支持包用来通告规则引擎的事件,包括规则激发,对象设置等等。这允许你将日志/审核动作从应用(及规则)的主要部分分离出来,因为事件是横切型的。

事件监听器有三种类型:WorkingMemoryEventListener、RuleFlowEventListener和AgendEventListener

Figure 2.27. WorkingMemoryEventListener

Figure 2.28. AgendaEventListener

Figure 2.29. RuEventListener

有状态和无状态Session都实现EventManager接口,这允许事件监听器被加入Session。

Figure 2.30. EventManager

所有的事件监听器都已经有默认的实现,它实现了每个方法,但是不做任何事情,你可以继承它们以减少要实现每个方法的花费——DefaultAgendaEventListener, DefaultWorkingMemoryEventListener, DefaultRuleFlowEventListener。下面的代码显示如何继承DefaultAgendaEventListener并加入Session——这个例子只是在规则激发时打印状态。

session.addEventListener( new DefaultAgendaEventListener() {

public void afterActivationFired(AfterActivationFiredEvent event) {

super.afterActivationFired( event );

System.out.println( event );

}

});

Drools也提供DebugWorkingMemoryEventListener, DebugAgendaEventListener 和DebugRuleFlowEventListener,它们在每一个实现的方法中打印系统状态。

session.addEventListener( new DebugWorkingMemoryEventListener() );

基于Rule IDE的Eclipse提供审计日志和图形视窗,这样规则引擎可以将日志事件记录下来,为以后查看和审计用。

2.10. 顺序模式

使用Rete,你有一个有状态的Session,在那里对象可以随时被设置或修改,规则也可以随时被增加和删除。现在我们假设一个无状态Session会发生什么样的情况呢?在完成了初始数据集后,没有更多的数据可以被设置或修改(没有规则的重新评估),并且没有规则可以在被增加或删除。这意味着我们可以认为引擎在这种情况下所处理的工作量是最小的。

使用salience和在规则集中的位置(只要仔规则终节点上设置一个sequence属性就可以)排序规则

建立一个数组,每一个可能的激活规则对应一个数组元素,元素的位置象征着激发的顺序

关掉除了右输入对象内存之外的所有节点内存

切断LeftInputAdapterNode的传播,并且在一个Command对象中有这个对象(LeftInputAdapterNode)和节点的索引,Command对象被加入WorkingMemory的列表中随后执行。

设置所有的对象,当完成断言后右输入节点内存检查Command列表并且按照顺序执行

所有激活结果应当被放置在数组中,按照规则的Sequence编号所决定的顺序。记录第一个和最后一个元素的位置,以减少遍历范围。

遍历激活规则的数组,按顺序执行元素

如果我们有一个允许执行规则的最大数,就能够尽早的退出网络评估而去激发数组中的规则

LeftInputAdapterNode不再去建立组元,增加对象然后传播这个组元,代替的是一个Command对象被建立并且增加到Working Memory内的一个列表中。这个Command对象保存了一个指向LeftInputAdapterNode的引用和要传播的对象。这在设置时停止了任何左输入的传播,因此我们知道一个右输入传播不再需要尝试与左输入进行合作(因为删除了左输入内存)。所有的节点内存被关闭,包括左输入组元内存,除了右输入对象内存——也就是说能够记住断言传播路径的节点只有右输入对象内存。一旦所有的断言完成并且所有的右输入内存配置完毕,我们就能遍历LeftInputAdatperNode Command对象列表,并按顺序调用;它们将通过网络向下传播,并尝试与右输入对象合作;而不需要再保存在左输入中,因为我们知道没有对象会再被设置并传播到右输入内存。

Agenda不再存在,不再用一个优先队列来组织这些组元,而是代替以一个规则数量长度的数组。RuleTerminalNode的sequence数值代表激活规则在数组中的位置。一旦所有Command对象准备完成,就可以遍历数组,依次检查每个元素并激发存在的规则。为了增加数组处理性能,我们记录最初和最后的单元位置。构造的网络中,每一个RuleTerminalNode被赋予了一个sequence数值,基于salience数值与它被加入到网络中的顺序。

右输入节点内存的典型结构是HashMap,为了更快的回收对象(我们知道事实上没有对象要回收),在对象的值没有索引时,我们可以使用列表。对于存在大量的对象情况,索引的HashMap提供性能的提升,如果我们已知对象只有很少的实例,那么对象列表的性能可能比HashMap索引要更好。

顺序(Sequential)模式只能被用于StatefulSession,并且默认是关闭的。要打开它可以通过将RuleBaseConfiguration.setSequentail设置为true,或者设置rulebase.conf文件中的drools.sequential特性为true。通过使用SequentialAgenda.SEQUENTIAL或SequentialAgenda.DYNAMIC调用setSequentialAgenda,或者配置"drools.sequential.agenda"特性,顺序(Sequential)模式可以回退成动态的agenda。

第三章. 安装和设置(Core 与IDE)

3.1. 安装和使用

Drools提供了基于eclipse的IDE,但这是可选的,Drools的核心只需要Java1.4(J2SE)。

一个简单的开始方法是下载和安装eclipse插件,它需要Eclipse GEF框架已经安转。插件将为你提供所有需要的依赖:你可以简单的建立一个新的规则项目,然后所有事情将会安排好。

关于规则工作台和IDE的详细内容请查阅后面的专述章节。安装eclipse插件就像普通插件一样,解压后放到插件目录即可。

Eclipse插件的使用不是必需的。规则文件能够以文本或表格的方式输入,规则工作台仅仅是提供方便性的一个工具。人们已经通过很多方式集成了工作引擎,并不存在要求用同一种方式。

作为一种可选的方式,你可以直接下载bin发布包,将它们包含在你的项目的classpath变量中。

3.1.1.  依赖库

Drools被分为几个模块,一些是用在规则的开发和编译中,另一些是用在运行时。在许多情况下,人们只想简单的包括运行时依赖的库文件。这样可以带来最大的灵活性。但是在某些情况下,我们需要将运行时的库引用文件尽可能的更小,比如将规则发布到二进制执行文件中。核心的运行引擎可以被压缩的很小,只需要两个jar文件约100k的大小。

以下是描述对于Jboss 规则引擎的重要库文件:

drools-core.jar – 核心引擎,运行时组件。包含了RETE引擎和LEAPS引擎。这个仅提供了运行时支持,当你已经将规则预编译好,并与Package或RuleBase对象发布时,这是运行时唯一需要依赖的。

drools-compiler.jar –包含编译和构建组件,从规则源码建立可执行的Rule Base。这个库是你的应用通常需要使用到运行时的依赖库,但在你已经预编译了规则后它不是必需的,只有drools-core.jar是必需的。

drools-jsr94.jar – 是对JSR-94(一种规则引擎访问规范)的一个实现,这本质上是一个在drools-compiler组件之上的层。需要注意的是,基于JSR-94规范,访问规则需要经过一系列复杂定义的接口调用。在可能情况下,直接使用Drools API会更加简单,但是在一些运行环境中遵循JSR-94规范是必须的。

drools-decisiontables.jar – 这是对决策表进行“编译”的组件,它需要与drools-compiler组件一起使用来完成编译。它可以提供对excel和CSV格式的输入的支持。

以上组件也引用了不少其它的依赖库,主要是drools-compiler, drools-jsr94 or drools-decisiontables模块需要其它引用库。当你运行在Java1.5环境中时,对XML库的引用可能是不需要的。其中关键的引用是“JCI”,它是apache java编译接口工具,提供运行时编译的能力;POI提供了表格解析的能力;Antlr提供了规则语言的语法分析能力。

注意:如果你正在J2EE或Servlet容器中使用Drools,并且你被JDT的classpath问题所烦恼,你可以选择使用janino编译器。设置系统属性"drools.compiler"为Ddrools.compiler=JANINO。

For up to date info on dependencies in a release, consult the README_DEPENDENCIES.txt file, which can be found in the lib directory of the download bundle, or in the root of the project directory.

3.1.2. 运行时(Runtime)

在这里讨论的运行时需求是指将规则使用二进制格式发布(使用Package或RuleBase对象)。这是一个可选的特性,使得保持你的运行环境为轻量级的。你可以使用drools-compiler在进程外产生规则包,然后将它们发布到运行状态的系统中。系统只需要drools-core.jar就可以执行规则包。这是一种可选的发布模式,许多人不需要将他们的系统进行这样的裁剪,但对某些环境来说是一个解决方案。

3.1.3. 安装IDE (规则工作台)

Eclipse中的规则工作台需要你有eclipse3.2以上版本,eclipse GEF 同样需要3.2以上。你可以通过现在插件或在线更新的方式安装它。

另一个可选的方式是使用Jboss IDE,它包含了预包装好的所有需要的插件,以及选择其他工具单独规则。你可以选择仅安装Jboss IDE自带的“bundle(包)”中的规则。

3.1.3.1. 安装GEF (必需的依赖库)GEF是eclipse图形化编辑框架,它为插件中的组件提供图像化显示功能。

如果你没有安装GEF,你可以选择使用更新机制或从eclipse.org网站下载。像其它大多数Eclipse的发布版本一样Jboss IDE已经有了GEF,因此对一些人来说这一步可以忽略。

首先你打开帮助菜单[Help->Software updates->Find and install]。然后选择Calisto更新站点:如果你没有使用Calisto,可以通过下面的链接下载GEF。

http://europa-mirror1.eclipse.org/tools/gef/update-site/releases/

接下来选择GEF插件:

按下[next]按钮,允许安装插件(可能需要重启eclipse)。一旦安装完成,你就可以继续安装规则插件。

3.1.3.2. 从zip压缩文件安装

从zip文件安装,需要先下载和解压文件。在zip文件中你将看到plugin目录,以及插件的jar文件。你可以选择将插件的jar文件放到你的eclipse应用的plugin目录,然后重启eclipse。

3.1.3.3. 从更新站点安装

使用更新站点是安装插件的一种便利方式,并且保持最新的版本(eclipse平台会自动检查需要的更新)。它是用来与重要更新和最新补丁同步的好办法。

一些防火墙可能造成eclipse连接更新站点困难,如果你遇到这个问题,那就采用手工方式安装插件。同样,如果你是使用手工安装插件的方式,那也需要手工的卸载。

步骤1:使用eclipse帮助菜单发现安抓特性

步骤2:选择“Search for new features to install”来安装新的插件(如果你是想检查已有插件的更新版本,使用另一个选项)。

步骤3: 屏幕显示已经在你的eclipse中定义好的更新站点

步骤4: 按下[New Remote Site…]按钮,在弹出的对话框中指定Name和URL
Name: "JBoss Drools"
URL: http://downloads.jboss.com/drools/updatesite/

http://anonsvn.labs.jboss.com/labs/jbossrules/updates/drools-ide-update/

步骤5: 选择你刚刚加入的更新站点。Eclipse在将来自动检查更新时将会包含这个站点。

步骤6: 你应该看到了从站点中返回的可用特性(Drools IDE)。

步骤7:许可证。选择接受许可证。一旦完成这步,工作台将开始下载。完成下载需要一点时间。

步骤8: 确认这是你想要的特性

步骤9: 接受组件是没有数字签名证书的提示(not been digitally signed)

步骤10: 完成后需要重启工作台

现在冲一杯咖啡,然后看一看在规则的工作区中有些什么

3.2. 从源码进行安装

Drools是开源软件项目,因此手册中提供如何从源码进行构建的指导。从源码构建意味着你可以使用到还在开发中,尚未正式发布的最新特性。虽然Drools是相当复杂的,但是许多人为此做出了贡献。

Drools在JDK1.5或更高的版本上工作。你仅需要安装下面列出的工具,版本是指最低的版本要求。

Eclipse 3.2

http://www.eclipse.org/

Subversion Client 1.3

http://subversion.tigris.org

http://tortoisesvn.tigris.org - recommended win32 client

Maven 2.0.7

http://maven.apache.org/

Ant 1.7.0

http://ant.apache.org

确认ant和java的可执行文件在合适路径下。例子给出对于win32系统的示范:

Path=D:\java\j2sdk1.5.0_8\bin;D:\java\apache-ant-1.7\bin;D:\java\maven2.0.7\bin

下面的环境变量也需要设置。同样是win32下的范例:

JAVA_HOME=D:\java\j2sdk1.5.0_8
ANT_HOME=D:\java\apache-ant-1.6.5
MAVEN_HOME=D:\java\maven2.0.7

过去发布版使用基于Ant的构建机制,但是现在maven是主要构建机制,虽然Ant在内部用作文档构建过程。

3.3. 源码Checkout

Drools 可以从两个Subversion(一种源码同步管理工具)库中下载:

匿名SVN

http://anonsvn.labs.jboss.com/labs/jbossrules/trunk/

开发者安全的SVN(需账户密码)

https://svn.labs.jboss.com/labs/jbossrules/trunk/

通过执行以下命令checkout Drools的源码。

fmeyer:~/jboss $ svn checkout http://anonsvn.labs.jboss.com/labs/jbossrules/trunk/ jbossrules

And wait to complete the files download.

A jbossrules/drools-repository

A jbossrules/drools-repository/.classpath

A jbossrules/drools-repository/.project

A jbossrules/drools-repository/doc

A jbossrules/drools-repository/doc/repository_layout.jpeg

A jbossrules/drools-repository/doc/high_level_design.jpeg

A jbossrules/drools-repository/doc/javadoc

A jbossrules/drools-repository/doc/javadoc/serialized-form.html

A jbossrules/drools-repository/doc/javadoc/index-all.html

A jbossrules/drools-repository/doc/javadoc/stylesheet.css

A jbossrules/drools-repository/doc/javadoc/allclasses-frame.html

A jbossrules/drools-repository/doc/javadoc/package-list

A jbossrules/drools-repository/doc/javadoc/overview-tree.html

A jbossrules/drools-repository/doc/javadoc/org

A jbossrules/drools-repository/doc/javadoc/org/drools

A jbossrules/drools-repository/doc/javadoc/org/drools/repository

A jbossrules/drools-repository/doc/javadoc/org/drools/repository/class-use

A jbossrules/drools-repository/doc/javadoc/org/drools/repository/class-use/RuleSet.html

A jbossrules/drools-repository/doc/javadoc/org/drools/repository/class-use/RulesRepositoryException.html

A jbossrules/drools-repository/doc/javadoc/org/drools/repository/class-use/RulesRepository.html

A jbossrules/drools-repository/doc/javadoc/org/drools/repository/RuleSet.html

....

snip

....

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/benchmark/waltz

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/benchmark/waltz/waltz.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/benchmark/manners

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/benchmark/manners/manners.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/benchmark/waltzdb

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/benchmark/waltzdb/waltzdb.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/TroubleTicketWithDSL.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/TroubleTicket.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/conway

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/conway/calculate.rfm

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/conway/generation.rf

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/conway/calculate.rf

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/conway/registerNeighbor.rfm

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/conway/killAll.rfm

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/conway/registerNeighbor.rf

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/conway/conway-agendagroup.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/conway/killAll.rf

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/conway/conway-ruleflow.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/conway/generation.rfm

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/ticketing.dsl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/StateExampleUsingSalience.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/golf.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/LogicalAssertionsNotPingPong.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/StateExampleDynamicRule.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/sudoku

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/sudoku/sudoku.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/HelloWorld.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/ExamplePolicyPricing.xls

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/HonestPolitician.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/Fibonacci.drl

A jbossrules/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/StateExampleUsingAgendGroup.drl

A jbossrules/drools-examples/drools-examples-drl/pom.xml

A jbossrules/drools-examples/drools-examples-drl/build.xml

U jbossrules

Checked out revision 13656.

虽然我们更加推荐使用命令行操作源码库,但是你也可以使用集成了SVN的Eclipse或者TortoiseSVN来工作。

设置TortoiseSVN从subversion代码库中checkout,如下所示:

3.4. 构建

3.4.1. 构建源码

现在我们已经获得了源码,下一步就是构建和安装源码。

自从使用Drools3.1版本开始构建系统。那里有两个Profile(指maven的xml设置文件中的profile定义块)是有用的,它启用了相关的模块"documentation" 和"eclipse";这能够让开发者快速的构建核心模块。Eclipse档案将下载eclipse到drools-eclipse目录,可能需要下载超过100MB内容(依赖你所使用的系统),无论怎样这个工作只需要进行一次;如果你希望你可以将下载的eclipse移动到另一个位置,那使用-DlocalEclipseDrop=/folder/jboss-rules/local-eclipse-drop-mirror来指定它。下面命令行是构建所有的jar文件,documentation和eclipse压缩到指定的本地目录以避免下载eclipse:

mvn -Declipse -Ddocumentation clean install -DlocalEclipseDrop=/folder/jboss-rules/local-eclipse-drop-mirror

你可以产生发布版本,它将所有东西放入压缩文件,如下所示:

mvn -Declipse -Ddocumentation clean install -DlocalEclipseDrop=/folder/jboss-rules/local-eclipse-drop-mirror

mvn -Ddocumentation -Declipse -Dmaven.test.skip package javadoc:javadoc assembly:assembly -DlocalEclipseDrop=/folder/jboss-rules/local-eclipse-drop-mirror

注意:安装(install)必须首先完成,因为javadoc:javadoc只有在jar文件已经在本地maven库中以后才工作,但是第二次运行时测试可以被忽略。要让assembly:assembly成功,应当为maven增加有效使用内存。在windows系统中下面的命令可以让它很好的工作:set MAVEN_OPTS=-Xmx512m

输入mvn clean清除之前产生的文件,然后测试和构建源码,并报告任何错误。

最后产生的Jar文件被放入项目根目录下的/target目录中。

当maven构建每一个模块后,它自动将产生的jar文件安装到本地maven的两个库中。这样它可以很容易的被其它项目的pom.xml所使用,或者复制到其它地方。

3.4.2. 构建使用手册

手册的构建现在也已经集成到maven构建过程里去了,并且可以通过使用profile (-Ddocumentation)开关或进入主目录中构建。手册也仍然可以进入documentation/manual目录通过ant命令行构建。

Drools使用Docbook规范编写手册。Ant用在maven内部构建文档,构建版本分为3种不同格式,都共享了相同的图片目录。

html_single

整个手册在一个单独的Html文件中

html

手册根据章节不同被分为多个文档

eclipse

文档适合被eclipse插件所包含。

手册可以通过在documentation目录中调用'mvn package',根据pom.xml配置产生,或者在构建源码时加上-Ddocumentation选项。通常产生的手册文档被复制到‘target/’目录。实际上发生的事情是maven调用一个独立的ant build.xml产生手册,位置在documentation/manual。文档在被复制到target目录前会放置在documentation/manual/build目录下。

fmeyer:~/projects/jbossrules/documentation $ mvn clean package

[INFO] Scanning for projects...

[INFO] ----------------------------------------------------------------------------

[INFO] Building Drools :: Documentation

[INFO] task-segment: [install]

[INFO] ----------------------------------------------------------------------------

[INFO] [antrun:run {execution: manual}]

[INFO] Executing tasks

[delete] Deleting directory /Users/fernandomeyer/projects/jbossrules/documentation/manual/build

clean:

all.doc:

lang.all:

lang.misc:

[copy] Copying 188 files to /Users/fernandomeyer/projects/jbossrules/documentation/manual/build/en/shared/images

[copy] Copying 1 file to /Users/fernandomeyer/projects/jbossrules/documentation/manual/build/en/shared/css

lang.dochtml:

[mkdir] Created dir: /Users/fernandomeyer/projects/jbossrules/documentation/manual/build/en/html

[copy] Copying 1 file to /Users/fernandomeyer/projects/jbossrules/documentation/manual/build/en/html

[java] Writing bk01-toc.html for book

[java] Writing pr01.html for preface(preface)

[java] Writing ch01s02.html for section

[java] Writing ch01s03.html for section

[java] Writing ch01s04.html for section

[java] Writing ch01s05.html for section

[java] Writing ch01s06.html for section

[java] Writing ch01.html for chapter

[java] Writing ch02s02.html for section

[java] Writing ch02s03.html for section

[java] Writing ch02s04.html for section

[java] Writing ch02s05.html for section

[java] Writing ch02.html for chapter

[java] Writing ch03s02.html for section

[java] Writing ch03s03.html for section

[java] Writing ch03s04.html for section

[java] Writing ch03s05.html for section

[java] Writing ch03s06.html for section

[java] Writing ch03s07.html for section

[java] Writing ch03s08.html for section

[java] Writing ch03s09.html for section

[java] Writing ch03.html for chapter

[java] Writing ch04.html for chapter

[java] Writing ch05.html for chapter

[java] Writing ch06s02.html for section

[java] Writing ch06s03.html for section

[java] Writing ch06s04.html for section

[java] Writing ch06s05.html for section

[java] Writing ch06.html for chapter

[java] Writing ch07s02.html for section

[java] Writing ch07s03.html for section

[java] Writing ch07.html for chapter

[java] Writing ch08.html for chapter

[java] Writing ch09.html for chapter

[java] Writing ch10s02.html for section

[java] Writing ch10.html for chapter

[java] Writing ch11.html for chapter

[java] Writing pt01.html for part

[java] Writing ix01.html for index

[java] Writing title.html for book

...snip ...

[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESSFUL

[INFO] ------------------------------------------------------------------------

[INFO] Total time: 51 seconds

[INFO] Finished at: Mon Jul 21 12:03:38 BRT 2007

[INFO] Final Memory: 5M/10M

[INFO] ------------------------------------------------------------------------>

产生的手册可以在‘target\drools-documentation$VERSION.jar'文件中找到,是一个包含所有格式的压缩文件。

手册在复制到目标位置前最初被产生到build目录中,如下所示:

3.5. Eclipse

3.5.1. 产生Eclipse项目

Drools项目为了方便而使用eclipse了项目格式。但是,这些最初是由maven2产生的。如果你已经安装了maven2,你也可以自动产生eclipse项目,甚至产生成IntelliJ等格式,细节查看下面说明(大多数人可以忽略这一章)。

Maven可以用来产生标准的Eclipse项目,但是它不能产生Eclipse插件项目。要为drools-core, drools-compiler 和drools-jsr94产生Eclipse项目输入'mvn eclipse:eclipse'。

3.5.2. 导入Eclipse项目

当产生了Eclipse项目文件后,现在可以将它们导入eclipse。当启动Eclipse时,将工作空间设置在你用subversion下载的文件的根位置。

我们调用'mvn install'后,项目所有的依赖库被下载并加入本地Maven库中。Eclipse不能发现这些依赖库,除非你告诉它maven的本地库在哪里。要完成这个,设置一个M2_REPO类路径变量。

3.5.3. 导出IDE插件

Drools-ide项目也已经被checked out,并准备好导出。

一旦插件被构建完成,打开输出目录并将jar文件复制到Eclipse的plugin目录中。

完成插件jar复制后,如果Eclipse处于打开状态,那它需要重新启动一次。启动后你会发现新的Drools菜单图标并且drl文件也有图标代表,并在编写时提供语法高亮和内容纠正(intellisense)功能。

3.5.4. 构建更新站点

对于插件也有一个更新用的站点。对于想更新这个插件更新站点的开发者,你需要获得更新站点项目(或者新建一个)。它们被保存在SVN中,但是是在/jbossrules/update目录,而不是/trunk目录中。它们是简单的eclipse特性和站点项目。

请记住,在下载目录中的插件是一个Zip文件,应该和更新站点一起被更新(因为它们都是获得相同插件的可选方法)。

Eclipse在feature中刷新插件,并且站点看起来没有工作,因此最好的办法是手工修改site.xml和feature.xml文件。要完成这个工作,打开drools-ide-update项目中的site.xml文件,它看起来如下:

<?xml version="1.0" encoding="UTF-8"?>

<site>

<!-- change both the jar and the version number, make sure the new features jar is named

the same as what you put in -->

<feature url="features/org.drools.ide_1.0.2.jar" id="org.drools.ide" version="1.0.2">

<category name="JBossRules"/>

</feature>

<category-def name="JBossRules" label="JBoss Rules"/>

</site>

更改feature中的version属性到一个新的版本号,同样jar文件也应该有一个新的版本号。

进入/feature目录,解压feature.jar获得feature.xml文件(feature.jar只包含feature.xml文件)。打开feature.xml文件,看起来如下:

<?xml version="1.0" encoding="UTF-8"?>

<feature

id="org.drools.ide"

label="Drools Rule Workbench"

version="1.0.2"> <!-- UPDATE THIS !! -->

<description>

JBoss Rules (Drools) Workbench for developers.

</description>

<copyright>

Copyright 2005 JBoss Inc

</copyright>

<license>

Licensed under the Apache License, Version 2.0(the &quot;License&quot;);

you may not use this file except in compliance with the License.

You may obtain a copy of the License at

 

http://www.apache.org/licenses/LICENSE-2.0

 

Unless required by applicable law or agreed to in writing, software

distributed under the License is distributed on an &quot;AS IS&quot; BASIS,

WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

See the License for the specific language governing permissions and

limitations under the License.

</license>

<plugin

id="org.drools.ide"

download-size="0"

install-size="0"

version="1.0.0"/> <!-- THIS JUST HAS TO BE CONSISTENT WITH THE PLUG IN -->

</feature>

改变FEATURE标签中的版本号与之前在site.xml中定义的相同。如果你改变了主要插件的版本号,你需要将版本号放在plug in标签中(它引用org.drools.ide插件)。然后压缩feature.xml到jar文件,文件名与你在site.xml中引用的相同。

最后将插件jar文件放入更新站点德/plugins目录(从之前的导出插件步骤中获得实际的插件)。现在你可以更新这个站点,它会在Eclipse客户端提示插件的新版本。

第四章. 决策表

4.1. 在电子表格中的决策表

决策表是一种精确而简洁的表达逻辑条件的方法,非常适用于业务级的规则。

Drools支持在电子表格中管理规则。支持的格式有Excel和CSV。这样你可以使用多种不同的表格程序,如Microsoft Excel,OpenOffice等。期望在将来的发布版本中可以支持基于Web的决策表编辑器。

决策表在软件中是很早就出现的概念,但是已经在多年的应用中被证明非常有用。简单的说,在Drools中的决策表通过表格中的数据直接产生规则。电子表格对于数据的捕获与操作是很有优点的。

4.1.1. 何时使用决策表

如果规则能够被表达为模板+数据的格式,那你应该考虑使用决策表。决策表中的每一行就是对应模板的一行数据,将产生一个规则。

许多商业用户已经使用电子表格来管理数据并进行计算。如果你有兴趣持续这条道路,你可以也通过这样的方式来管理规则。这假设你有兴趣使用xls或csv文件来管理规则包。决策表不建议在规则不能遵循模板或者只有很少量的规则的时候使用。使用决策表的好处在于你可以控制规则的哪些参数可以被修改,而不用直接暴露规则。

决策表也为隐含在下面的对象模型提供了一定的隔绝性。

4.1.2. 概述

这里有一些现实中决策表的例子。

在上面的例子中,决策表用到的技术被隐藏起来,仅仅显示一个标准的表格特性。

规则从17行开始(每一行产生一个规则)。条件等式在C,D,E..列(下一幅图中的黄色格子是代表行为)。你可以看到在单元格内的值是非常简单的,它们的含义可以从16行的表头看出。B列只是一个描述。习惯上在表格的不同功能部分使用各自的颜色区分。

注意,虽然决策表看起来像是从上到下的处理,但这不是必须的。如果能够将规则以与顺序无关的方式实现是非常好的,简单的说它使得维护变得简单,你不需要花时间来维护行之间的顺序。因为每一行是一个规则,同样的原理被应用。当规则引擎处理fact,任何匹配的规则都有可能被激发(一些人对这个不太理解,当一个规则被激发,引擎会清空agenda空间并且在第一个匹配的位置模拟一个非常简单的决策表)[译者注:有可能一个激发的规则引起另一个规则不能被激发,但哪一个规则先被激发是无顺序的]。同样注意,你可以在电子表格中建立多个表格,这样当规则共享相同的模板时,它们可以被编组,但是在最后一个电子表格中的规则会被放到同一个规则包中。决策表是用来快速有效的自动产生规则的一个工具。

4.1.3. 决策表如何工作

对于决策表要记住的关键点是,每一行是一个规则,行中的每一列是规则的条件或行为。

电子表格查找“RuleTable”关键字作为一个规则表的开始(行与列的开始)。其它关键字被用于定义包级别的属性(后面说明)。必须要保证关键字在单独的一列中。习惯上使用第二列“B”列作这个,但是它可以是任何列(习惯上在列左边留下空白为注释使用)。在下图中,C列是规则数据实际开始的列,所有在左边的信息都被忽略。

如果我们展开被隐藏的部分,它开始显露出工作的原理。注意在C列上的关键字。

现在你可以看到使得决策表工作的隐藏的魔法。RuleSet关键字显示被用在规则包中的名称,所有的规则会归入关键字右边那个单元格的包中(名称是可选的,它将有一个默认值,但你必须使用RuleSet关键字)。在列C上的另一个关键字是Import,Sequential稍后再说明,可以留意到关键字通过名称/值配对的方式使用。RuleTable关键字非常重要,因为它指明后面跟着的是基于某个规则模板的规则数据块。在RuleTable关键字后面跟着一个名称,这个名称用来指明产生规则名所使用的前缀,行号会加在这个前缀后形成独立规则名。RuleTable列指示规则数据从这一列开始,左边的列都会被忽略。

查阅第14行(紧跟在RuleTable后面的一行):关键字CONDITION和AND说明在这一列下面的数据是用在规则的LHS还是RHS部分。规则中的其它属性也可以选择通过这种方式设置。

15行包含对象类型的声明,该行内容是可选的,如果你不打算使用它,必须留下一个空行,你将来可能会用到它。当你使用这一行,在单元格的下面一行(16行)变成对上面的对象类型的约束。在上面的例子中会产生Person(age==" 42"),这里的42来自18行。在上面的例子中,“==”是隐含的(如果你只输入一个字段名,它会假设你在寻找精确的匹配)。也要注意的是,通过将列合并可以产生一个跨列的ObjectType声明,这意味着所有在合并范围下的列将被合并到一个ObjectType的约束中。

16行包含着他们自己的规则模板:注意它们可以使用$param占位符代表下面单元格中的数据所在的位置(你可以使用$param或$1、$2来定义参数,参数顺序通过单元格内的逗号分隔确定)。17行被忽略,它是规则模板的描述。

18到19行显示出来的数据,将被15行的模板合并产生规则。如果一个单元格中没有数据,则该模板被忽略(它意味着条件或动作不对那一行使用)。规则行将会顺序读下去,直到遇到一个空行。你可以在一个表中放置多个RuleTable。21行包含了另一个关键字和值,像这样的关键字所在的行位置是不重要的(大多数人将它放在顶部),但是关键字所在的列位置必须与RuletTable或RuleSet关键字所在列相同(在这个例子中C列被选中,但是你想的话也可以使用A列)。

在上面的例子中,将产生如下的规则:

//row 18

rule "Cheese_fans_18"

when

Person(age=="42")

Cheese(type=="stilton")

then

list.add("Old man stilton");

end

注意[age=="42"] 和 [type=="stilton"]是作为单个约束加入上面声明的ObjectType中,如果声明ObjectType的两个单元格被合并,则这两个约束条件也会合并到一个ObjectType中。

4.1.4. 关键字和语法

4.1.4.1. 模板的语法

在模板中的语法要做什么,依赖于它是条件列还是行为列。在大多数情况下,它与DRL中的LHS和RHS的含义相同。这意味着在LHS中必须使用约束语言,而在RHS中是一段要执行的代码。

"$param"占位符被用在模板里表示单元格中的数据应当如何代入模板。你也可以用"$1"达到同样的效果。如果单元格包含了以逗号分隔的值列表,使用$1和$2这样的格式,指出列表中顺序的数据应当插入哪个位置。

例:

如果模板是[Foo(bar == $param)]而单元格是[ 42 ],则结果是[Foo(bar == 42)]。

如果模板是[Foo(bar < $1, baz == $2)]单元格是[42,42],则结果是[Foo(bar > 42, baz ==42)]

对于条件表达式:如何产生约束依赖于片断行上有什么声明(之上会有ObjectType声明)[译者注:结合后面的示例,这里应该是指在ObjectType的下一行是片断行,比如age,age<]。如果有,则片断作为ObjectType的一个独立约束产生。如果没有,它们仅仅使用值进行替代。如果你输入的是一个简单字段,则假设为相等判断。如果你放了另外的操作符在字段后面,则值会被加在最后面,或者寻找$param进行替换。

对于推论:如何产生也依赖于片断行所作的声明。如果没有任何声明,则简单的进行单元值代入。如果有声明(通常是上例中示范的绑定变量或全局变量),则将作为一个对象上的函数调用被附加(查阅前例)。

使用下面的例子让你可以更容易理解:

上面显示Persion对象声明跨越了两列,则产生的规则约束应当是Person(age == ... , type == ...)。如前所述,当片断行上仅有字段名时,强制为相等测试。

上面的条件示例显示如何使用替代方式取得在片断中的值。在这个例子中结果是Person(age == "42"))。

上例显示如果你在片断最后放置了一个操作,则值会被自动加在操作后面。

你可以在列前面放置一个绑定(上图是在ObjectType单元增加绑定,约束的绑定可以在下面的单元type前增加)。你可以在ObjectType行放置任何信息,它可以是列的前置条件。

该例显示推论如何能够通过简单的值替代进行(对该片断留空即可,对于条件列也同样,片段行为空,则直接使用值填充),通过这个规范,你可以在推论中放置任何你想要的,不仅仅是一个方法调用。

4.1.4.2. 关键字

下表列出了与规则表结构相关的关键字

Table 4.1. Keywords

关键字说明是否必须
RuleSet在这个单元的右边单元中包含ruleset的名称必须,只能有一个(如果如果留空,则使用默认值)
Sequential右边的单元可以是true或false,如果是true则确保规则按照从表格的上面到下面的顺序执行可选
Import右边单元格包含以逗号分隔的,要导入规则库中的类的列表可选
RuleTable一个以RuleTable开头的单元格代表一个规则表定义的开始。实际的规则表从下一行开始。规则表的读取遵循从左到右,从上到下的顺序,直到出现空行。至少一个,如果有多个,全部加入同一个RuleSet中
CONDITION说明该列用于规则的条件表达式每个规则表至少一个
ACTION说明该列用于规则的推论部分每个规则表至少一个
PRIORITY说明该列值将用来设定该行规则的'salience'值,覆盖'Sequential'标记可选
DURATION说明该列将用于设定该行规则的持续时间可选
NAME说明该行规则的名称将使用该列所指定名称可选
Functions紧跟在右边的单元格包含可以用在规则代码段中的函数声明。Drools支持在DRL中定义函数,允许逻辑在规则中嵌入,不用通过硬编码改变。可选
Variables紧跟在右边的单元格包含global声明。格式是类型跟着变量名,如果需要多个变量使用逗号分隔可选
UNLOOP如果单元格这一列上有值,则no-loop属性被设置可选
XOR-GROUP在这一列中的单元值被认为是规则所属于的XOR/activation group的组名。Activation group意味着该组中只要有一个规则执行,其它规则的执行都被取消可选
Worksheet默认只有第一个工作表被认为是决策表N/A

4.1.5. 基于决策表建立并集成电子表格

操作基于决策表的电子表格的API在drools-decisiontables模块中。其中只有一个类需要关注:SpreadsheetCompiler。这个类将获取不同格式的电子表格,并产生DRL格式的规则(然后你就可以象通常情况一样使用)。如果你想要,你可以使用SpreadsheetComiler产生局部的规则文件,然后将它加入一个完成了fact设置的规则包中(这允许你分离技术与非技术方面的规则)。

作为开始,你可以建立一个一个样本电子表格,然后基于它开发。另外如果你在使用插件(规则工作台),其中的向导能够为你产生一个电子表格样本(为了进行修改,你需要使用一个xls兼容的电子表格编辑器)。

4.1.6. 在决策表中管理业务规则

4.1.6.1. 工作流程与交互

电子表格可以通过业务工具很好的建立。通过决策表可以隔断IT人员与领域专家的交互(针对提取规则而言),使得业务分析师能够清晰了解业务规则,是进行关系分离的理想办法。

典型的,整个编制规则的过程(使用新的决策表)如下:

业务分析师获取一份决策表模板,从库中或IT人员处获得。

决策表业务语言描述被输入表中

规则概述输入决策表

决策表交给技术人员,他将业务语言(规则概述)映射为脚本(如果是一个新的应用或数据模型,可能包含软件开发)

技术人员与业务分析师一起进行复查修改

业务分析师可以继续按需要修改规则行

同时,技术人员可以为规则开发测试用例(与业务分析师交流),这些测试用例可以在系统运行后被用来确认规则的有效性以及规则修改的影响。

4.1.6.2. 使用电子表格特性

你可以使用像Excel这样的程序的功能特性为输入数据时提供辅助,例如校验字段。你可以使用列表在其它WookSheet中保存单元的有效值,像下图所示。

一些应用提供有限的历史修改记录,但推荐使用版本控制工具。因此当你对规则进行修改时,旧的版本被归档(有许多开源工具支持版本控制,如Subversion)。

http://www.drools.org/Business+rules+in+decision+tables+explained

 

第五章. 规则工作台 (IDE)

5.1. Introduction

Jboss规则工作台作为一个Eclipse插件发布,它允许你在Eclipse中编制并管理规则,将规则与你的应用集成。这是一个可选工具,并且不是所有的组件都需要使用,你可以使用对你有用的组件。工作台的其它好处有,可以降低管理规则所需要的技能要求(如允许业务分析师复查和管理规则),所有这些基于Eclipse平台。

这个指南将涉及Jboss规则的一些特性,有关工作台需要接触的方面(它假设阅读者已经熟悉规则引擎和Drools的细节)。需要注意的是,规则引擎没有什么潜在的特性是需要依赖于Eclipse,可以自由的集成到你选定的工具中。

你可以获得工作台插件的Zip文件,或者从一个更新站点中安装(参考安装部分所描述)。

Figure 5.1. Overview

5.1.1. 特性概要

规则工作台有如下特性:

文字/图形规则编辑器

编辑器理解DRL语义,并提供内容协助(包括一个概要视图)

向导...

帮助你快速建立规则库项目

新建规则源文件

新建DSL文件

DSL编辑器

建立并管理从用户语言到规则语言的映射

规则校验

当规则被输入,在后台构造规则,并报告错误

你可以通过Eclipse架构查看以上特性。所有Eclipse的功能都可以使用。

5.1.2. 建立规则项目

新项目向导的目标是设置一个可执行的基础项目,以立刻开始使用规则。该项目将包括一个基本的架构,classpath、示范规则和测试用例。

Figure 5.2. 新规则项目

Figure 5.3. 建好的新项目

新建的规则项目在src/rules目录中包含一个规则示范文件(Sample.drl)以及在src/java目录中包含一个java文件(DroolsTest.java)可以用来在Drools引擎中执行该规则,都属于com.sample包。所有其它的jar引用是在执行过程中需要的,并且被加入了一个自定义的classpath容器中,称为Drools库。规则并不一定需要被保存在java项目中,这仅仅是方便已经使用Eclispe作为Java开发平台的程序员。

重要提示:Drools增加了一个称为“Drools Builder”的插件在你的Eclipse项目实例中。这意味着你可以在任何项目中启用构建器,它将在资源改变时构建和校验你的规则。这一步骤在规则项目向导中自动发生,但是你也可以手工在任何项目中启用它。另一方面,如果你的项目中存在大量规则(每个文件大于500条),后台构建器在每次变更时对规则重新构建将耗费大量时间。你可以选择关闭构建器或者将大量的规则放入以rule作为扩展名的文件,则你仍然可以使用规则编辑器,但是构建器不会在后台进行规则构建。这样的话,为了确认规则的有效性,你需要在单元测试中全面的测试它们。

5.1.3. 新建规则向导

你可以简单的新建一个空白的以drl扩展名结尾的文本文件,或者使用向导完成这件事。向导菜单可以通过Ctrl+N激活,或者从工具条上选择它(将会有一个菜单跟随Jboos规则图标)。

Figure 5.4. 向导菜单

为产生规则源文件向导需要你指定一些基本信息选项。这些仅仅是默认,你可以在晚一点的时候改变你的想法。规则存放的位置,通常是在顶级目录下建立一个rules目录保存,并保存在其下的合适子目录名中。包名是强制性的,就像java中的包一样(是一个将所有规则打包在一起的名称空间)。

Figure 5.5. 新规则向导

这个向导产生一个规则的工作架构。对于所有的向导来说,只要你不想用就不需要使用它们。

5.1.4. 规则编辑器

规则编辑器是规则管理员和程序员花费最多时间的地方。规则编辑器在eclipse中遵从一个普通文本编辑器的模式,具有所有文本编辑器的特性。规则编辑器提供弹出的窗口作为内容助理。你可以通过按下Ctrl+Space来激活该窗口。

Figure 5.6. 规则编辑器

规则编辑器工作在以drl为扩展名的文件上。规则通常使用一个规则包进行分组。当然也可以将同组的规则放在不同的文件中,但是必须要使用同样的包名。这些DRL文件是无格式文本文件。

你从上面的例子可以看到,包中使用了DSL(注意expander关键字,它告诉规则编译器去查找指定名称的DSL文件)。甚至于DSL文件同样以无格式文本进行保存,这样可以进行更简单的规则管理和版本管理(对规则实例进行版本比较)。

编辑器提供了一个Outline视图,是于规则的结构保持同步的(在保存时更新)。这提供了使用名字快速浏览规则的途径,对于在文件中可能有几百个规则的情况。这些条目默认按照字母顺序排列。

Figure 5.7. 规则 outline 视图

5.1.5. 视图

当使用Drools引擎调试应用时,有三个新的视图可以用来查看引擎的状态:Working Memory View(工作空间视图), Agenda View(议程视图)和 Global Data View(全局变量视图)。为了能够使用这些视图,需要在用于激活工作空间的代码中建立断点。例如,进行workingMemory.fireAllRules()调用的行是一个好地方。如果调试器在那个连接点暂停,你接着在调试变量视图中选择working memory变量。下面的规则可以用来列出所选working memory的细节:

Working Memory视图显示所有被设置到Drools working memory中的元素

Agenda视图显示所有在agenda中的元素。对每一个在Agenda中的规则,规则名以及绑定的变量被显示出来

Global Data视图显示当前定义在Drools working memory中的所有全局数据

Audit视图使用树形视图来显示在规则引擎执行过程中记录的日志事件。

5.1.5.1. The Working Memory View工作空间视图

Working Memory视图显示所有被设置到Drools working memory中的元素。

一个动作图标被加到视图的右边,用来定制显示什么:

[显示逻辑结构]图标指定显示在working memory中元素的逻辑结构,或所有细节。逻辑结构允许对元素进行可视化的设置。

5.1.5.2. The Agenda View议程视图

Agenda视图显示所有在agenda中的元素。对每一个在Agenda中的规则,规则名以及绑定的变量被显示出来。

一个动作图标被加到视图的右边,用来定制显示什么:

[显示逻辑结构]图标指定显示agenda条目的逻辑结构,或所有细节。逻辑结构允许对元素进行可视化的设置。AgendaItem显示当前正在被处理的规则以及所有参数的值。

5.1.5.3. The Global Data View全局数据视图

Global Data视图显示当前定义在Drools working memory中的所有全局数据一个动作图标被加到视图的右边,用来定制显示什么:

[显示逻辑结构]图标指定显示在working memory中元素的逻辑结构,或所有细节。逻辑结构允许对元素进行可视化的设置。

5.1.5.4. The Audit View审计视图

Audit视图用来观察在规则引擎执行期间记录的日志信息。使用如下代码建立审计日志:

WorkingMemory workingMemory = ruleBase.newWorkingMemory();

// 建立新的 Working Memory 日志, 日志被记录到文件中

WorkingMemoryFileLogger logger = new WorkingMemoryFileLogger(workingMemory);

// event.log文件在日志目录中新建,目录必须存在

logger.setFileName("log/event");

workingMemory.assertObject( ... );

workingMemory.fireAllRules();

// 停止日志

logger.writeToDisk();

通过点击[打开日志]图标(Audit视图的第一个动作图标)。Audit视图显示了规则执行过程中的所有事件。其中有六种事件类型,每一种使用不同的图标:

Object 被设置(绿色方块)

Object 被修改(黄色方块)

Object 被删除(红色方块)

Activation created激活规则建立 (向右箭头)

Activation cancelled激活规则取消 (向左箭头)

Activation executed激活规则已执行 (蓝色方块)

所有这些事件展示了与这些事件相关的额外信息,像在working memory事件中(设置,修改,删除)的对象的id和toString的显示内容,规则的名称和所有在激活事件(建立,取消,执行)中的待激发规则的绑定变量。当执行一个激活规则时如果激发了事件,则该事件作为激活规则事件的一个子节点显示。对于一些事件,你可以返回 “导致激发的原因”

对象被修改或删除,是由该对象最后的事件所导致的。这或者是对象设置事件,或者是该对象的最后的对象修改事件。

导致一个待激活规则被取消或执行的是相应待激活规则的建立事件

当选择一个事件时,导致该事件的原因使用绿色显示在Audit视图中(如果是可见的)。你也可以右击并选择“Show Cause”菜单项,将滚动Audit视图到导致该事件激发的原因处。

5.1.6. 领域规范语言DSL

DSL允许你建立一种,让你的规则读起来像是自然语言一样的规则语言。典型的,你可以看看业务分析师如何使用他们的语言描述规则,然后通过规则约束将这些映射到你的对象模型。这样做的好处是可以在你的领域对象和规则之间建立一个隔绝层。当规则增加时DSL也需要增加阿,最好的方法是通用术语通过不同的参数重复使用。

规则工作台提供了针对DSL的编辑器(DSL以无格式文本方式保存,因此你可以使用任何一种编辑器)。该编辑器将在任何DSL扩展名为结尾的文件中激活。也有一个建立简单DSL的向导。

5.1.6.1. 修改语言

Figure 5.8. DSL编辑器

DSL编辑器为语言表达式映射到规则表达式提供了一个表格视图。语言表达式是用在规则中的。这同样为规则编辑器的内容助理提供信息,因此内容助理可以从DSL配置中提取出建议的语言表达式(当规则编辑器打开DRL文件时,DSL配置也被读取)。规则语言映射(Rule language mapping)列是为规则进行‘编码’,语言表达式(language expression)列将会被规则引擎编译器编译为‘编码’形式。规则语言的格式决定于它是规则的条件部分还是推论部分(可能是一段Java代码)。范围(Scope)列指定表达式的目标:是作为规则LHS的when,还是作为then?或者其它地方?

通过选择映射条目(表中的一行),你可以在下面灰色字段处看到表达式和映射。双击或按下[edit]按钮,将打开修改对话框。你可以删除条目或新增一个(一但你知道表达式不再有用,通常应该删除它)。

Figure 5.9. 规则映射编辑对话框

如何工作:"Language expression"被用来解析规则语言,依赖于被设置的"scope"。当在规则中发现表达式时,被花括号括住的值{value}从规则源码中提取出来。然后这个值被代入"Rule mapping"表达式中,基于花括号中名称相同的原则。因此在上例中,自然语言表达式被映射到Person类型的两个约束中({age}和{location})。如果scope 是"then",Rule mapping可能是一个java表达式。如果在drl的某一部分你不想使用语言映射,则在行前面加上前缀 “>”,编译器就不会进行转换。要注意DSL是可选的。当规则语言被编译时,dsl文件也需要有效。

5.1.7. The Rete视图

Rete树型视图为你的drl文件展示了当前Rete网络的结构。只要点击DRL编辑器左下角的[Rete Tree]选项卡就可以。然后你可以产生当前Rete网络的可视模型。你可以在视图上推或拉节点,以安排你的Rete网络到最佳模式。如果视图上有上百个节点,使用一个方框选中它们,然后可以对它们分组。你可以放大或缩小视图,如果当前视图没有显示所有的节点。使用"+" 和"-"两个按钮。

在当前版本中没有将Rete视图导出为gif或jpg图像的功能。请使用屏幕打印功能获得当前Eclipse的截图,然后剪切出需要的部分。

该图是使用Java Universal Network/Graph Framework (JUNG)建立的。Rete视图是一个高级特性,它仍然在实验状态。它使用Eclipse内部的Swing绘制,将来可能使用SWT或GEF进行增强。

Rete视图仅工作在Drools规则项目中,在那里Drools Builder被设为项目的属性。

如果你在其它类型的项目中使用Drools,没有一个使用Drools Builder的规则项目,你可以建立一个小的工作区:

设置一个小的Drools规则项目与上面项目相邻,将需要的库和你想查看Rete视图的DRL文件导入。在DRL编辑器右下边的选项卡上,通过点击“Generate Rete View”来查看。

5.1.8. 大容量DRL文件

依赖于你使用的JDK,它可能需要增加permanent generation的最大值(permanent generation是JVM用来保存class object和meta data的地方)。SUN和IBM的jdk有一个permanent generation,而BEA JRockit没有。

为了增加permanent generation,启动eclipse时使用:-XX:MaxPermSize=###m

示例: c:\eclipse\eclipse.exe -XX:MaxPermSize=128m

拥有4000条规则的RuleSet要将permanent generation至少设为123MB。

(注意这个permanent generation大小在进行海量规则编译时也适用,通常每个规则需要一个或多个类)

作为另一种选择,你可以将规则放置在扩展名为rule的文件中,后台的构建器将不会在每次变更时进行编译,如果你的IDE因为大量的规则变得响应迟钝时,这个可以提供更好的性能。

5.1.9. 调试规则

你可以在Drools应用执行的过程中调试规则。你可以在规则的推论部分增加断点,当在规则执行时遇到这样的断点,执行被暂停。你可以检查在断点时变量的情况,并且使用调试功能决定下一步如何执行(停止或继续)。你也可以使用调试视图观察working memory和agenda的内容。

5.1.9.1. 建立断点

你可以使用两种方法在drl文件中增加/删除断点,类似向Java文件中增加断点:

在DRL编辑器中双击你希望增加断点的那一行。注意断点只能在规则的推论中建立。在不允许增加断点的地方双击没有任何效果。断点可以通过再次双击来删除。

右键点击规则,会弹出一个菜单,包含"Toggle breakpoint"操作。注意断点只能在规则的推论中建立。在不允许增加断点的地方会自动使该操作无效。再次选择该操作会使得断点无效。

调试状态时包含一个断点视图,可以看到所有定义的断点,获得它们的属性,可以启用/禁用或删除它们。

5.1.9.2. 调试规则

Drools断点只有在应用是作为Drools应用调试时才能起作用。你可以如下设置:

选择应用的启动类。右击并选择"Debug As >"子菜单。然后选择"Debug ..."菜单打开一个用于创建,管理和运行Debug配置的对话框

在左边的树中选择"JBoss Rules Application"条目,并点击["New launch configuration"]按钮(在树上面工具条中最左边的图标)。这将建立一个新的配置,并且已经填充了一些属性在里面(如项目和启动类),基于你一开始选择的类。在这里显示的所有属性与标准Java程序一样。

将调试配置的名称改的有意义一些。对于其它属性,尽管保留默认值就可以了。对于这些属性的更多信息请查阅Eclipse JDT文档。

点击[Debug]按钮开始调试你的应用

你只需要定义调试配置一次,下一次运行Jboss规则应用时,你不需要再建立一个新的配置,只有选择之前你已经定义好的那个,接着点击[Debug]按钮就可以了。Eclipse工具条也包含快捷按钮,用来快速重新执行你之前定义的配置。

在点击[Debug]按钮后,应用开始执行,直到遇到断点后暂停。这可以是一个Drools规则断点或其它标准的java断点。当遇到一个Drools规则断点后,相应的DRL文件被打开,并且高亮显示激活的行。变量视图会包含规则的所有参数和它们的值。你可以使用默认的Java调试操作来决定下一步动作(继续,中止,单步调试等等)。Debug视图也可以用来监控working memory和agenda的内容,你不用去选择一个working memory,当前执行的那个被自动显示。

第六章. 规则语言

6.1. 概述

Drools4.0自带一种非XML格式的规则语言。这种格式通过使用标点符号,使得语言非常的轻量化,并且通过DSL(域规则语言)支持自然语言的扩展,这使得你可以将该语言演化到与你需要解决的问题域更为相配。这章主要介绍Drools3自带的规则语言。其中所用的图称为线路图,是对语言中术语解释的基本流图表。对该技术非常有兴趣的话,你可以查阅“drl.g”,那是Antlr3为规则语言提供的语法分析。如果你使用规则工作台,那许多规则结构可以通过内容辅助系统完成,例如输入‘ru’然后按下Ctrl+Space,它将为你建立一个规则结构。

6.1.1. 规则文件

规则文件通常是以drl扩展名结尾。在一个drl文件中可以包含多个规则,函数等等。但是你也可以将规则分开到多个规则文件中(在这种情况下建议采用.rule扩展名,但不是必需的),分散规则利于管理巨量规则的情况。DRL是简单的text文件格式。

规则文件的完整结构是:

Example 6.1. 规则文件

package package-name

imports

globals

functions

queries

rules

这些元素的声明顺序不重要,处理package的名称如果声明的话必须是规则文件的第一个元素。所有的元素都是可选的,因此你只要用你所需要的就行了。我们将在下面的内容中对它们逐一讨论。

6.1.2. 规则的构成

规则具有如下主体结构:

rule "name"
attributes
when
LHS
then
RHS
end

规则的结构是非常简单的,许多符号都是不需要的,甚至“name”两边的引号也是可选的。ATTRIBUTES(通常是可选项)指出规则的行为表现。LHS是规则的条件部分,它遵循下面将提到的语法。RHS是允许Java语义代码(很快将支持其它语言和C#)执行的块。仅有的特别的关键字是为了设置,删除和修改facts所用。任何在LHS中绑定的变量可以在RHS中使用。

特别注意的是,空白是不重要的,除非在DSL中使用,在DSL中每一行会先于下一行处理(空白在这里可能有很重要的作用)。

6.1.3. 保留字

在规则语言中使用了一些保留字。避免在你的领域对象,属性,方法,函数以及规则的其它部分中使用保留字是明智的。接下来的列表是你应该在规则内容中避免使用的保留字(如果使用了,大多数时候可以正常工作,但有时会引起解析错误)。当然你可以将这也关键字用作方法等定义的一部分,例如notSomething(),这样没有问题。

下面的保留字是在编写规则时应当尽力避免使用的:

rule

query

when

then

end

null

and

or

not

exists

collect

accumulate

from

forall

true

false

eval

下面的列表是你在编写规则时应当尽量避免使用的,但是如果你不得不在某些地方使用它们,语法分析器也可以正常工作。

package

function

global

import

template

attributes

enabled

salience

duration

init

action

reverse

result

contains

excludes

memberOf

matches

in

date-effective

date-expires

no-loop

auto-focus

activation-group

agenda-group

dialect

rule-flow-group

当然,你可以将它们用作方法名称的一部分,如notSomething()——这完全没有问题。

6.2. Comments注释

注释用来标记那些需要被规则引擎忽略的内容。当遇到它们时会被抛弃,除非在语言代码块中,如规则的RHS部分。

6.2.1. 单行注释

Figure 6.1. 单行注释

你可以使用'#' 或者'//'建立单行注释。语法分析器会自动忽略注释的内容。

rule "Testing Comments"

when

# this is a single line comment

// this is also a single line comment

eval( true ) # this is a comment in the same line of a pattern

then

// this is a comment inside a semantic code block

# this is another comment in a semantic code block

end

6.2.2. 多行注释

Figure 6.2. 多行注释

多行注释被用来对一段文本进行注释,无论在代码块内外都可以使用,例如:

rule "Test Multi-line Comments"

when

/* this is a multi-line comment

in the left hand side of a rule */

eval( true )

then

/* and this is a multi-line comment

in the right hand side of a rule */

end

6.3. Package

包是规则以及其它相关结构的一个集合,如import和global。包的成员应该彼此有一定联系,如人力资源的规则包。一个包通过名称空间描绘,这样很好的保持了一组规则的独立性。包名也就是名称空间名,与文件或目录名称无关。

运行时的规则包可以从多个规则源码处组装,当组装完成时,所有的规则被放入一个顶级的Package配置中。但是不可能将不同名称的Package的内容放入同样的Package资源下。在一个RuleBase上可以建立多个Package。通常情况是将所有同名Package下的规则放在一个文件中(这样它就是完全自包含的)。

下面的蓝图显示一个包中可能包含的所有组件。注意,包必须有一个名称空间,并且使用标准的java约定进行命名;例如包名不允许空格,不像规则名称可以有空格。除了package和expander标记必须放在文件的顶部,在其它规则之前,其它关键字元素的使用没有任何顺序要求,可以使用在文件的任何地方。分号是可选用的。

Figure 6.3. package

6.3.1. import

Figure 6.4. import

Import标记就像java中的含义一样。对于任何要用在规则中的对象,你需要指定完整的路径和类型名。Drools从同名的java包中自动导入类。

6.3.2. expander

Figure 6.5. expander

Expander标记是可选的,用来指定DSL配置(通常保存在独立文件中)。这为解析器提供了如何理解你自定义的规则语言。要注意的是在Drools4.0中(这里与Drools3.x中不同),expander声明强制工具提供你的上下文环境并且避免错误报告,但是API允许用编程方式附加DSL模板,如果expander没有声明在源文件中。

6.3.3. global全局变量

Figure 6.6. global

Global是全局变量。如果多个包定义了同样名称的全局变量,它们必须使用同样的类型,并且全部指向同一个全局值。全部变量通常用来返回数据,如一个动作的记录,获得提供数据或服务给规则使用。Global不会插入到Working Memory中,因此当全局变量发生改变时,引擎不会得知;因为这个原因,全局变量不能用在条件约束上,除非这个值不会发生改变。在条件约束中错误使用全局变量会导致意想不到的结果。Globals是全局的变量。他们常用来将应用程序的对象提供给规则使用,通常是将数据或服务提供给规则使用(指定使用在规则推论中的应用服务),从规则处返回数据(如日志或在规则推论中增加的值)或者从规则中对应用进行回调。全局变量不会被插入Working Memory,因此它们从来不会参与推论,如果全局变量是一个不变的常量,则只在LHS中使用它们。引擎不会通知和跟踪全局变量的值变更。不正确的在约束中使用全局变量会带来让人惊讶的结果——糟糕的惊讶,就像医生对你的X光片说“那里有些有趣的东西”一样。

如果多个Package声明了同样的全局变量,那么它们必须有相同的类型,并指向同一个全局变量。

为了使用全局变量,你必须:

在规则文件中声明全局变量并使用它,如:

global java.util.List myGlobalList;

rule "Using a global"

when

eval( true )

then

myGlobalList.add( "Hello World" );

end

在working memory上设置全局变量的值。最好是在将fact插入working memory之前设置完所有全局变量,如:

List list = new ArrayList();

WorkingMemory wm = rulebase.newStatefulSession();

wm.setGlobal( "myGlobalList", list );

注意,这些全局变量只是从你的应用传到Working Memory的对象的实例。这意味着你可以传入任何你想要的对象:你可以传递一个服务的位置代理,或者可能是一个服务本身。随着新的‘from’元素现在可以传递一个Hibernate Session对象作为全局变量,允许‘from’通过一个命名的Hibernate查询中将数据拉进来。

可能的一个例子是Email服务。在你的规则引擎的集成代码中,你获得你的email服务对象,并且将它引入到working memory。在DRL文件中,声明你已经有了一个EmailService类型的全局变量,并给它一个名称为email。然后在规则的推论中,你就可以使用类似email.sendSMS(number,message)这样的调用。

全局变量不是被设计用来在规则之间共享数据的,并且它们永远都不应该用于这个目的。规则经常对working memory进行推论和删除fact,因此如果你想在规则间共享数据,将数据插入working memory就可以。

从规则中设置全局变量的值是非常不合适的,我们建议你在应用程序中通过working memory的接口设置这个值。

6.4. Function

Figure 6.7. function

相对于正常的java类,函数是在你的规则代码中放置语言代码的方法。它们不可能做任何超过你可以在帮助类(在java中定义,被设置入规则的Working Memory中的类)中做到的事情(实际上,编译器为后面的场景产生帮助类,那样帮助不大)。主要使用函数的优点是可以将逻辑保存在一个地方,并且你可以在需要的时候改变函数(这样做各有优缺点)。函数最大的用处是被规则的推论(then)部分中的行为所调用,特别是当一个行为操作需要反复被调用时——如发送邮件。

典型的函数声明如下所示:

function String hello(String name) {

return "Hello "+name+"!";

}

注意function关键字的使用,虽然它并不真的是java的一部分。传入function的参数就像普通的方法一样(如果不需要参数可以为空)。返回值的概念也和普通方法相同。

作为使用函数的另一种方法,你可以在辅助类中使用一个静态方法:Foo.hello()。Drools4.0支持静态方法导入,因此你只需要做下面的事情:

import static my.package.Foo.hello

对于上面的情况,要使用函数只需要在推论或代码块中通过函数名称来调用它,例如:

rule "using a static function"

when

eval( true )

then

System.out.println( hello( "Bob" ) );

end

6.5. Rule

Figure 6.8. rule

规则指定“when”作为一系列条件的集合(称为LHS),然后在“then”中指定一系列操作(称为RHS)。一个用户经常问的问题是“为什么使用when代替if”。“when”之所以取代“if”是因为“if”通常是程序执行过程中的一部分,在某一个特定的时间点它对条件进行检查。而“when”代表着它不约束在特定的评估顺序或时间点,在引擎生命周期的任何时候“when”都可以执行。

规则必须有一个名称,并且在一个包中是唯一的。如果你在同一个DRL中定义同名规则两次,在装载时将产生一个错误。如果你新增的DRL包含一个存在于Packaage中的规则,那新的规则将替换旧规则。如果规则名称中有空格,需要使用双引号包含(好习惯是定义名称时都使用双引号)。

特性是可选的,最好保持每行一条,如下描述:

规则的LHS部分跟随when关键字(when最好在单独的一行上),RHS部分跟随then关键字(最好也单独一行)。规则使用end关键字结尾。规则不能进行嵌套。

Example 6.2. 规则语法

rule "<name>"

<attribute>*

when

<conditional element>*

then

<action>*

end

Example 6.3. 规则示例

rule "Approve if not rejected"

salience -100

agenda-group "approval"

when

not Rejection()

p : Policy(approved == false, policyState:status)

exists Driver(age > 25)

Process(status == policyState)

then

log("APPROVED: due to no objections.");

p.setApproved(true);

end

6.5.1. Rule 属性

规则属性提供了影响规则行为的一种声明式的方法,有些十分简单,而另一些是复杂子系统的一部分,如规则流。要从Drools中获得最大的收获,你必须十分的了解每一个属性。

Figure 6.9. rule attributes

6.5.1.1. no-loop

默认值 : false

类型 : Boolean

当规则在推论中对fact进行修改后,可能会导致该规则的重新激活,引起递归。设置no-loop为true可以阻止该规则被再次激活。

6.5.1.2. salience

默认值 : 0

类型 : integer

每一个规则有一个整数类型的优先级属性,默认为0,这个整数可以使正负数。优先级数字高的规则会比优先级低的规则先执行。

6.5.1.3. agenda-group

默认值 : MAIN

类型 : String

Agenda group允许用户对分隔Agenda执行区提供更多的控制。只有在具有焦点的agenda group中的规则才能够激发。

6.5.1.4. auto-focus自动获取焦点

默认值 false

类型 : Boolean

当规则的auto-focus属性为true时,如果该规则符合激活条件,则该规则所在agenda-group自动获得焦点,允许规则激发。

6.5.1.5. activation-group

默认值 :N/A

类型 : String

在同名activation-group中的规则将以互斥的方式激发。这个意思时在这个组中第一条被激发的规则将取消其它规则的激发,即使它们已经在激发队列中。Activation-group属性可以是任何字符,只要所有你需要放在同一个组中的规则中的activation-group属性是相同的即可。

注:这个组之前被称为Xor-group,但是从技术上来说它与Xor并不完全一样,但是当你听到别人说xor-group时,你知道这就是activation-group。

6.5.1.6. dialect

默认值 : 由Package指定

类型: String

可能值: "java" or "mvel"

Dialect指定在LHS代码表达式或RHS代码块中使用的语言。当前两种语言有效,Java和MVEL。Dialect可以在Package级别统一指定,而Rule属性中指定的dialect将局部覆盖掉Package中的定义。

6.5.1.7. date-effective

默认值 : N/A

类型: String, 包含日期/时间定义

规则只能在date-effective指定的日期和时间之后激活。

6.5.1.8. date-exptires

默认值 : N/A

类型: String, 包含日期/时间定义

如果当前时间在date-expires指定的时间之后,规则不能激活。

6.5.1.9. duration

默认值 : N/A

类型: long

Duration指出规则将在指定的一段时间后激发,如果那个时候规则的激活条件还是处于true的情况下。

Example 6.4. Some attribute examples

rule "my rule"

salience 42

agenda-group "number 1"

when ...

6.5.2. LHS (when) 条件元素

LHS是规则条件部分的常用名称。它包含0个或更多的条件元素。如果LSH是空的,那它被重写为eval(true),这意味着规则一直是true状态,并且将在working memory一建立时就被激发。

Figure 6.10. Left Hand Side

Example 6.5. Rule Syntax Overview Example

rule "no CEs"

when

then

<action>*

end

在内部被重写为:

rule "no CEs"

when

eval( true )

then

<action>*

end

条件元素工作在一种或多种模式下(将在下面介绍)。常用的一种是“and”,它是当LHS中的多个条件元素之间没有任何连接时的默认情况。注意在“and”之前不能使用像“or”这样的声明,你想一下就知道这是必然的。一个声明只能指向一个单独的Fact,当‘and’被满足是,它匹配超过一个fact——哪一个是它绑定的fact呢?

6.5.2.1. Pattern 模式

模式元素是条件元素中最重要的。下面的实体关系图给出了一个构建模式约束的不同部分以及它们是如何一起工作的一个概要图,每一个都会使用蓝图和例子详细说明。

Figure 6.11. 模式实体关系图

在ER图的最顶部,你可以看到模式包含0..n个约束,并且可以有一个可选的模式绑定。下面的蓝图显示了关于这点的语法。

Figure 6.12. Pattern

在最简单情况下,没有约束,它只是简单的匹配一个类型,下面的例子中这个类型是“Cheese”。这意味着模式将匹配working memory中的每一个Cheese对象。

Example 6.6. Pattern

Cheese( )

为了能够引用匹配的对象,使用一个模式绑定变量如‘$c’。变量的前缀使用的$是可选的,但是在复杂的规则中它会很方便用来区别变量与字段的不同。

Example 6.7. Pattern

$c : Cheese( )

在模式的圆括号范围内是所有操作发生的地方。一个约束可以是字段约束,内部Eval(在3.0中称为断言)或一个约束组。约束之间可以使用‘,’, '&&' 或者 '||'符号分隔。

Figure 6.13. Constraints

Figure 6.14. Constraint

Figure 6.15. Group Constraint

逗号(,)被用来分隔约束组,它隐含着‘and’的连接语法。

Example 6.8. 逗号连接的约束组

# Cheese 类型是stilton并且price < 10并且age==mature.

Cheese( type == "stilton", price < 10, age == "mature" )

上面的例子是有三个约束组,每一个是单独的约束条件:

group 1: type is stilton -> type == "stilton"

group 2: price is less than 10 -> price < 10

group 3: age is mature -> age == "mature"

'&&' (and) 和'||' (or)约束连接符允许约束组有多个约束,如:

Example 6.9. && 和|| 约束连接符

Cheese( type == "stilton" && price < 10, age == "mature" ) // Cheese type is "stilton" and price < 10, and age is mature

Cheese( type == "stilton" || price < 10, age == "mature" ) // Cheese type is "stilton" or price < 10, and age is mature

上面例子有两个约束组,第一个有两个约束而第二个组有一个约束。

连接符按照以下顺序求值,从高到低:

&&

||

,

在任何逻辑或数学表达式中,可以通过圆括号来改变求值的顺序。如:

Example 6.10. 使用圆括号改变求值优先级

# Cheese type is stilton and ( price is less than 20 or age is mature ).

Cheese( type == "stilton" && ( price < 20 || age == "mature" ) )

在上面的例子中,圆括号使得||连接符在&&连接符之前被求值。
要特别注意的是,'&&'和逗号虽然有着同样的语义,但是逗号不能被包含在复杂的约束表达式中。

Example 6.11. 不等价的连接

Cheese( ( type == "stilton", price < 10 ) || age == "mature" ) // 无效!因为 ',' 不能用于表达式绑定

Cheese( ( type == "stilton" && price < 10 ) || age == "mature") // 有效!&&可以用在表达式绑定中

6.5.2.1.1. Field Constraints 字段约束

字段约束指定用于字段名上的约束条件;字段名可以带有一个可选的变量绑定。

Figure 6.16. fieldConstraint

有三种类型的约束,单值约束,复合值约束和多重约束

Figure 6.17. restriction

6.5.2.1.1.1. JavaBeans 作为 facts

字段是对象上的可访问的方法。如果你的模型对象遵循java bean模式,那么字段是通过"getXXX" 或"isXXX"方法暴露的(这些方法没有参数,并返回数据)。你可以使用bean名称约定来访问字段("getType"可以使用"type"访问)——我们使用jdk标准的Introspector类来进行映射。

例如,对于Cheese类,下面的:Cheese(type == ...)使用了类实例中的getType()方法。如果字段名不能发现,它就会将名称尝试当作一个没有参数的普通方法调用;在Cheese对象实例中的"toString()"方法可以写为Cheese(toString == ..)——使用方法的全名以及正确的大小写。请确认你所访问的方法是不带参数的,并且是仅用于访问的(在内部,不会因为调用这些方法改变了对象的状态,而这个状态可能会影响规则——记住规则引擎为了提高效率,在调用之间缓存了匹配的结果)。

6.5.2.1.1.2. Values

字段约束能够使用很多值,包括字符串,限定标识符(qualifiedIdentifier),变量和返回值

Figure 6.18. literal

Figure 6.19. qualifiedIdentifier

Figure 6.20. variable

Figure 6.21. returnValue

当你需要的时候,可以使用== 和 != 与"null"关键字一起检查字段是否为null,如:Cheese(type != null)。如果字段为null,求值器不会抛出异常,并且返回true。强制转换(Coercion-是指语言允许一个类型的值用到期望另一个类型的上下文中,语言实现时,要执行从源类型到目标类型的自动隐式转换。)会一直检查是否字段和值是不同的类型,如果强制转换尝试失败则抛出异常。例如:如果"ten"在数值求值中作为String提供,在那里"10"将被强制转换为数字10。强制转换以字段类型为标准而不是值类型。

6.5.2.1.1.3. 单值约束

Figure 6.22. singleValueRestriction

6.5.2.1.1.3.1. Operators

Figure 6.23. Operators

有效的操作依赖于字段的类型。通常它们基于数据类型是自说明型的,例如,对于日期字段,"<"意味着之前等等。"Matches"只用于string字段,"contains" 和"not contains"只用于集合类型字段。这些操作可以用在任何值上并强制转换为求值器所需要的正确值(将值按照字段类型转换)。

Matches 操作

字段匹配(Matches)用于任何有效的Java正则表达式。正则表达式对象(Regexp)通常是String,但是也允许使用能够有效分析为regexp的变量。注意Matches在这里不同于Java中的是,如果你直接在源文件中编写一个String正则表达式,不需要转码'\'字符。例如:

Example 6.12. 正则表达式约束

Cheese( type matches "(Buffalo)?\S*Mozerella" )

Not Matches 操作

Example 6.13. 正则表达式约束

Cheese( type not matches "(Buffulo)?\S*Mozerella" )

Contains 操作

'contains' 用来检查是否字段的集合或数组包含指定的对象。

Example 6.14. Contains with Collections

CheeseCounter( cheeses contains "stilton" ) // contains with a String literal

CheeseCounter( cheeses contains $var ) // contains with a variable

not containts用来检查是否字段的集合或数组不包含指定的对象。

Example 6.15. Literal Constraints with Collections

CheeseCounter( cheeses not contains "cheddar" ) // not contains with a String literal

CheeseCounter( cheeses not contains $var ) // not contains with a variable

注意: 为向后兼容性,'excludes'作为'not contains'的一个同义字提供。

memberof

'memberof' 用来检查字段是否是集合或数组的成员;集合必须是绑定的变量。

Example 6.16. Literal Constraints with Collections

CheeseCounter( cheese memberof $matureCheeses )

not memberof

'not memberof'用来检查字段是否不是集合或数组的成员;集合必须是绑定的变量。

Example 6.17. Literal Constraints with Collections

CheeseCounter( cheese not memberof $matureCheeses )

6.5.2.1.1.3.2. 字符串约束

字符串约束是最简单的约束格式,将字段与指定的字符串求值:数值,日期,string或者boolean。

Figure 6.24. literalRestriction

字符串约束使用'=='操作,因为我们使用hash索引,因此提供更快的执行速度。

Numeric

支持所有标准的Java数据元类型

Example 6.18. Numeric Literal Restriction

Cheese( quantity == 5 )

Date

默认支持"dd-mmm-yyyy"这样的日期格式。你可以通过在系统特性("drools.dateformat")中提供另一种日期掩码来自定义格式。如果需要更多的控制,使用inline-eval约束。

Example 6.19. Date Literal Restriction

Cheese( bestBefore < "27-Oct-2007" )

String

允许任何有效的Java String

Example 6.20. String Literal Restriction

Cheese( type == "stilton" )

Boolean

只能使用true 或 false。0 和 1 不能识别, 也不允许Cheese ( smelly )这样的表示(省略了等值判断)。

Example 6.21. Boolean Literal Restriction

Cheese( smelly == true )

Qualified Identifier

Enum也可以使用,jdk1.4 和jdk5风格的enum都被支持——对于jdk5必须在jdk5环境下执行。

Example 6.22. Boolean Literal Restriction

Cheese( smelly == SomeClass.TRUE )

6.5.2.1.1.3.3. Bound Variable Restriction 绑定变量约束

Figure 6.25. variableRestriction

变量可以绑定到Fact和它们的字段,然后在后面的字段约束中使用。绑定变量被称为声明。有效的操作符由被约束的字段类型决定;在那里会进行强制转换。绑定变量约束使用'=='操作符,因为能够使用hash索引,因此提供非常快的执行速度。

Example 6.23. Bound Field using '==' operator

Person( likes : favouriteCheese )

Cheese( type == likes )

'likes'是我们的变量(声明),它绑定到任何匹配的Person实例的favouriteCheese字段,并被用在下面Cheese的类型约束中。任何有效的java变量并都可以使用,包括'$',这通常用来显示变量与字段的区别。下面的例子显示绑定到模式对象类型的实例的变量,并使用'contains'操作,注意这次使用了'$'。

'likes' is our variable, our Declaration, that is bound to the favouriteCheese field for any matching Person instance and is used to constrain the type of Cheese in the following Pattern. Any valid java variable name can be used, including '$'; which you will often see used to help differentiate declarations from fields. The exampe

Example 6.24. Bound Fact using 'contains' operator

$stilton : Cheese( type == "stilton" )

Cheesery( cheeses contains $stilton )

6.5.2.1.1.3.4. Return Value Restriction 返回值约束

Figure 6.26. returnValueRestriction

返回值约束可以使用任何有效的Java元数据类型或对象。要避免使用任何Drools关键字作为声明标识。在返回值约束中使用的函数必须返回静态常量(time constant)结果。之前声明的绑定可以用在表达式中。

Example 6.25. Return Value Restriction

Person( girlAge : age, sex == "F" )

Person( age == ( girlAge + 2) ), sex == 'M' )

6.5.2.1.1.4. Compound Value Restriction 复合值约束

复合值约束用在可能有多个允许值的时候,当前只支持'in' 和'not in'两个操作。这些操作使用圆括号包含用逗号分开的值的列表,它可以是变量,字符串,返回值或限定标识符。'in' 和'not in'运算式实际上被语法分析器重写成多个!= and ==组成的多重约束。

Figure 6.27. compoundValueRestriction

Example 6.26. Compound Restriction using 'in'

Person( $cheese : favouriteCheese )

Cheese( type in ( "stilton", "cheddar", $cheese )

6.5.2.1.1.5. Multi Restriction 多重约束

多重约束允许你对一个字段通过使用'&&' 或者'||'约束连接符进行多个约束条件的判断。允许使用圆括号分组,它会让这种约束看起来更自然。

Figure 6.28. multiRestriction

Figure 6.29. restrictionGroup

Example 6.27. Multi Restriction

Person( age > 30 && < 40 ) // simple multi restriction using a single &&

Person( age ( (> 30 && < 40) || (> 20 && < 25) ) ) // more complex multi restriction using groupings of multi restrictions

Person( age > 30 && < 40 || location == "london" ) // mixing muti restrictions with constraint connectives

6.5.2.1.2. Inline Eval Constraints 内联的Eval约束

Figure 6.30. Inline Eval Expression

Inline-eval约束可以使用任何有效的语言表达式,只要它最终能被求值为boolean元数据类型——避免使用任何Drools关键字作为声明。表达式必须是静态常量(time constant)。任何在当前模式之前定义的变量都可以使用,自动代入(autovivification)机制用来自动建立字段绑定变量。当构建器发现标识不是当前定义的变量名是,它将尝试将它作为对象的字段来访问,这种情况下,构建器自动在inline-eval中建立该字段的同名变量。

下例将找到所有男性/女性配对,其中男性比女性大两岁;boyAge变量是使用自动代入(autovivification)机制建立的。

Example 6.28. Return Value operator

Person( girlAge : age, sex = "F" )

Person( eval( girlAge == boyAge + 2 ), sex = 'M' )

6.5.2.1.3. Nested Accessors 嵌套访问

Drools允许在字段约束进行嵌套访问,通过使用MVEL访问符号。包含嵌套访问的字段约束实际在内部重写为MVEL语言的inline-eval。当使用嵌套访问时必须注意,Working Memory不知道任何被嵌套的值,也不知道它们何时发生变化;在它们的父引用被插入Working Memory后,它们就应该被认为是不再发生变化的。如果你希望修改嵌套的值,那首先应当删除父对象,然后重新插入它。如果你在对象图的根部只有一个父对象,当使用MVEL语言时,你可以使用modify关键字,它在需要重新删除和插入根父对象时阻止setter方法写入嵌套的访问器。嵌套访问器可以被用在操作符的任何一边。

Example 6.29. Nested Accessors

$p : Person( )

Pet( owner == $p, age > $p.children[0].age ) // Find a pet who is older than their owners first born child

is internally rewriten as an MVEL inline eval:

$p : Person( )

Pet( owner == $p, eval( age > $p.children[0].age ) ) // Find a pet who is older than their owners first born child

注意: 嵌套访问器比直接访问字段花费更多的性能资源,因此谨慎的使用它们。

6.5.2.2. 'and'

'and'条件元素用来将其它条件元素组合在一起。LHS的根元素是一个隐含的前缀And元素,不需要指定。Drools支持前缀和中缀符,但是前缀是首选的,因为编组时默认使用前缀的,这样避免了混淆。

Figure 6.31. prefixAnd

Example 6.30. prefixAnd

(and Cheese( cheeseType : type )

Person( favouriteCheese == cheeseType ) )

Example 6.31. implicit root prefixAnd

when

Cheese( cheeseType : type )

Person( favouriteCheese == cheeseType )

中缀‘and’与圆括号在一起进行显式的分组。‘&&’符合作为‘and’的同义选择,不推荐使用,但是作为向后兼容原因它仍然在语言中提供。

Figure 6.32. infixAnd

Example 6.32. infixAnd

Cheese( cheeseType : type ) and Person( favouriteCheese == cheeseType ) //infixAnd

(Cheese( cheeseType : type ) and (Person( favouriteCheese == cheeseType ) or Person( favouriteCheese == cheeseType ) ) //infixAnd with grouping

6.5.2.3. 'or'

‘or’条件元素用来组织其它条件元素。Drools支持前缀和中缀符,但是前缀是首选的,因为编组是默认使用前缀的,这样避免了混淆。‘Or’条件元素的行为与约束和字段约束中的限制中使用的‘||’连接符是不同的。引擎实际上并不理解‘or’条件元素,而是用许多不同的逻辑转换,规则被重写为许多的子规则;转换后的规则将单个‘or’作为根节点并且每个子规则有一个逻辑结果。每个子规则可以像正常的规则一样激活和激发,在子规则直接没有交互的行为——这可能给新的规则编制者带来很大的混淆。

Figure 6.33. prefixOr

Example 6.33. prefixOr

(or Person( sex == "f", age > 60 )

Person( sex == "m", age > 65 )

中缀‘or’与圆括号一起使用。‘||’符号作为or的同义操作,不推荐使用,只是因为历史的原因在语法中保留。

Figure 6.34. infixOr

Example 6.34. infixOr

Cheese( cheeseType : type ) or Person( favouriteCheese == cheeseType ) //infixOr

(Cheese( cheeseType : type ) or (Person( favouriteCheese == cheeseType ) and Person( favouriteCheese == cheeseType ) ) //infixOr with grouping

‘or’条件元素也允许使用模式绑定,这意味着每一个结果子规则将绑定它的模式到模式绑定。

Example 6.35. or with binding

pensioner : (or Person( sex == "f", age > 60 )

Person( sex == "m", age > 65 ) )

也允许显式绑定每一个模式。

(or pensioner : Person( sex == "f", age > 60 )

pensioner : Person( sex == "m", age > 65 ) )

‘or’条件元素从多个规则中产生结果,称为子规则,对每一个可能的逻辑输出。上面的例子将在内部产生的两个规则中获得结果。这两个规则独立工作在Working Memory中,这意味着都可以进行匹配,激活和激发——那里没有简捷的方式。

最好的考虑方法是将OR条件元素看成产生两个额外规则的快捷方式。当你按照这种思路思考时,对于一个规则如果OR两边的条件都为true时能够被激活多次就很清晰了。

6.5.2.4. 'eval'

Figure 6.35. eval

Eval本质上是一个大杂烩(catch all),它允许任何语义代码被执行,只要最后返回一个boolean值。这可以涉及在规则LHS中绑定的变量和在规则Package中定义的函数。使用eval可以减少规则的声明情况以及用一个低性能的引擎获得结果。‘eval’可以在模式的任意位置使用,但最好的方式是作为规则LHS中最后的条件元素增加。

Eval不能被索引,因此不能对使用eval的字段约束进行优化。因此最好只是在函数的返回值随时发生变化的情况下使用它。普通的字段约束中不能调用drools中定义的函数。

对于熟悉Drools2.x体系的用户,旧的Drools参数和条件标签等同于绑定变量到适当的类型,然后在一个eval节点中使用它。

Example 6.36. eval

p1 : Parameter()

p2 : Parameter()

eval( p1.getList().containsKey(p2.getItem()) )

eval( isValid(p1, p2) ) //只是如何在LHS中调用函数的方法 – 函数名是 "isValid"

6.5.2.5. 'not'

Figure 6.36. not

'not'是一阶逻辑的存在判断量词,检查在Working Memory中某些Fact的存在性。括号是可选的。将‘not’理解为那里必须没有任何…。

Example 6.37. No Busses

not Bus()

Example 6.38. No red Busses

not Bus(color == "red")

not ( Bus(color == "red", number == 42) ) //brackets are optional

not ( Bus(color == "red") and Bus(color == "blue")) // not with nested 'and' infix used here as ony two patterns

6.5.2.6. 'exists'

Figure 6.37. exists

'exists'是一阶逻辑的存在判断量词,检查在Working Memory中某些Fact的存在性。将exist理解为至少有一个…。如果你将模式与exist一起使用,那么不管在working memory中有多少数据匹配约束,规则只激活一次。

Example 6.39. Atleast one Bus

exists Bus()

Example 6.40. Atleast one red Bus

exists Bus(color == "red")

exists ( Bus(color == "red", number == 42) ) //brackets are optional

exists ( Bus(color == "red") and Bus(color == "blue")) // exists with nested 'and' infix used here as ony two patterns

6.5.2.7. 'forall'

Figure 6.38. forall

Forall条件元素在Drools中完全支持一阶逻辑。Forall条件元素在所有匹配最初模式的fact也同时匹配后面的模式的情况下为true。

rule "All english buses are red"

when

forall( $bus : Bus( type == 'english')

Bus( this == $bus, color = 'red' ) )

then

# all english buses are red

end

在上面的规则中,我们选择所有类型为english的Bus对象,然后对每一个匹配的fact进行接下来的模式匹配,如果它们都匹配,则forall条件元素被求值为true。

要声明在working memory中给定类型的所有fact必须匹配一组约束,forall可以简单的使用单模式。

Example 6.41. Single Pattern Forall

rule "All Buses are Red"

when

forall( Bus( color == 'red' ) ) // 这里省略了最初的Bus()模式

then

# all asserted Bus facts are red

end

上面的例子与下面的多重模式写法类同:

Example 6.42. Multi-Pattern Forall

rule "all employees have health and dental care programs"

when

forall( $emp : Employee()

HealthCare( employee == $emp )

DentalCare( employee == $emp )

)

then

# all employees have health and dental care

end

forall可以嵌套在其它条件元素中组成完整的表达式。例如:

Example 6.43. Combining Forall with Not CE

rule "not all employees have health and dental care"

when

not forall( $emp : Employee()

HealthCare( employee == $emp )

DentalCare( employee == $emp )

)

then

# not all employees have health and dental care

end

作为一个边注,forall条件元素等价于写为:

not( <first pattern> and not ( and <remaining patterns> ) )

要注意的是forall是一个范围界定符,因此在它之前定义的变量都可以使用,但是在它之内绑定的变量不能用在外面。

6.5.2.8. From

Figure 6.39. from

‘from’条件元素允许你为模式声明一个推论的来源。这允许引擎使用不在Working Memory中的数据进行推论。源数据可能是绑定变量的子字段,或者方法调用的结果。它是一种强大的结构,允许在盒子(指Drools的运行环境)的外面与其它应用程序组件和框架集成。一个常见的例子是使用hibernate命名查询从数据库中返回需要的数据。

用来定义对象源的表达式可以是任何符合MVEL语法的表达式。例如,它允许你容易的使用对象特性导航,执行方法调用以及访问映射和集合元素。

这是绑定其它模式子字段的一个简单例子:

rule "validate zipcode"

when

Person( $personAddress : address )

Address( zipcode == "23920W") from $personAddress

then

# zip code is ok

end

利用Drools引擎提供的新的表示方法带来的灵活性,你可以用很多办法来解决这个问题。下面是同样的结果,但是用“.”来实现。

rule "validate zipcode"

when

$p : Person( )

$a : Address( zipcode == "23920W") from $p.address

then

# zip code is ok

end

前面的例子在单模式上推论。Form元素也支持返回对象集合的对象源。在那种情况中,from将遍历集合中所有对象,并尝试单独匹配每一个对象。例如,如果我们希望规则对订单中的每一个项目增加10%的折扣,可以如下:

rule "apply 10% discount to all items over US$ 100,00 in an order"

when

$order : Order()

$item : OrderItem( value > 100 ) from $order.items

then

# apply discount to $item

end

上面的例子将导致每一次遇到给定订单的任一个项目的值大于100时都激发规则。

下一个例子举例如何在一个hibernate查询结果上进行推论,Restaurant模式将依次在每一个结果上进行推论和绑定。

p : Person( )
Restaurant( food == p.favouriteFood )
from hs.getNamedQuery( "list restaurants by postcode" )
.setProperties( [ "postcode" : p.address.zipcode ] )
.list()

6.5.2.9. 'collect'

Figure 6.40. collect

Collect条件元素允许规则对从给定的源或WorkingMemory中的对象集合进行推论。在一阶逻辑术语中这称为Cardinality Quantifier。下面是简单的例子:

import java.util.ArrayList

rule "Raise priority if system has more than 3 pending alarms"

when

$system : System()

$alarms : ArrayList( size >= 3 )

from collect( Alarm( system == $system, status == 'pending' ) )

then

# Raise priority, because system $system has

# 3 or more alarms pending. The pending alarms

# are $alarms.

end

在上面的例子中,规则将为每一个给定的系统在working memory中寻找所有未解决的警报,并将它们在ArrayList中分组。如果对一个系统发现3个以上的警报,规则将激发。

Collect条件元素结果模式可以接受任何实现了java.util.Collection接口并提供默认的无参数构造函数的类。例如你可以使用默认的java集合如ArrayList、 LinkedList、 HashSet等等,或者你自己定义的类,只要它符合上面的条件。

源模式和结果模式都可以被约束为任何其它模式。(译者注:保留原文。Both source and result patterns can be constrained as any other pattern.)

在collect条件元素前的绑定变量中的元素是在源与结果模式的范围内,你可以使用它们限制你的源和结果模式。但是,collect是一个范围界定符,意味着在其内的任何绑定不能在它的外面使用。

Collect接受嵌套的from元素,因此下面的例子是有效的collect使用:

import java.util.LinkedList;

rule "Send a message to all mothers"

when

$town : Town( name == 'Paris' )

$mothers : LinkedList()

from collect( Person( gender == 'F', children > 0 )

from $town.getPeople()

)

then

# send a message to all mothers

end

6.5.2.10. 'accumulate'

Figure 6.41. accumulate

Accumulate条件元素是更灵活更强大的集合条件元素模式,它可以完成collect所能做到的所有事情,并且能做到collect无法完成的一些事情。基本上,accumulate所做的是迭代集合中所有对象,对每一个对象执行自定义操作,最后返回结果对象。

accumulate条件元素常用语法:

<result pattern> from accumulate( <source pattern>,

init( <init code> ),

action( <action code> ),

reverse( <reverse code> ),

result( <result expression> ) )

每个元素的含义如下:

<source pattern>: 是普通的模式,引擎尝试匹配每一个源对象

<init code>: 是使用所选择语言编写的代码块,在开始遍历源对象集合之前对每个组元执行一次

<action code>:是使用所选择语言编写的代码块,对每个源对象执行一次。

<reverse code>:是使用所选择语言编写的可选代码块,对不再匹配源模式的每一个对象执行。这个代码块的目的是‘undo’任何在<action code>中进行的计算,因此引擎可以在源对象被修改或删除时进行递减计算,很大的提高了这些操作的性能。

<result expression>:是使用所选择语言编写的代码块,在所有源对象遍历后执行。

<result pattern>:使用所选择语言编写的代码块,匹配从<result expression>中返回的对象。如果匹配,accumulate条件元素求值为true,并且引擎处理在规则中下一个条件元素的求值,如果不匹配,accumulate条件元素求值为false,并且引擎停止那条规则的求值。

如果看一个例子更容易理解:

rule "Apply 10% discount to orders over US$ 100,00"

when

$order : Order()

$total : Number( doubleValue > 100 )

from accumulate( OrderItem( order == $order, $value : value ),

init( double total = 0; ),

action( total += $value; ),

reverse( total -= $value; ),

result( total ) )

then

# apply discount to $order

end

在上面的例子中,对每一个在Working Memory中的Order对象,引擎首先将total变量初始化为0。然后开始遍历Order对象中的所有OrderItem对象,并执行操作(在这个例子中,它将所有OrderItem的价格累加到total变量中)。完成对一个Order中所有OrderItem的遍历后,它将返回结果表达式计算的结果(在上面的例子中就是total变量的值)。最后引擎将会尝试用Number模式匹配结果,如果这个数值大于100,规则将激发。

例子选择Java作为编写语言,因此注意在init,action和reverse代码块中都使用了分号。Result中是一个表达式,因此它不需要分号。如果用户使用了其它的语言,他必须遵守该语言的特定语法。

之前提到,reverse代码块是可选的,但是强烈建议用户使用它,以在更新或删除时获得性能的提升。

Accumulate条件元素能够用来对源对象执行任何操作。下面的例子示例对自定义对象的操作:

rule "Accumulate using custom objects"

when

$person : Person( $likes : likes )

$cheesery : Cheesery( totalAmount > 100 )

from accumulate( $cheese : Cheese( type == $likes ),

init( Cheesery cheesery = new Cheesery(); ),

action( cheesery.addCheese( $cheese ); ),

reverse( cheesery.removeCheese( $cheese ); ),

result( cheesery ) );

then

// do something

end

6.5.2.10.1. Accumulate Functions

Accumulate条件元素是非常强大的,但是它通过使用预定义函数(称为Accumulate函数)可以更容易使用并具有更好的声明性。Accumulate函数像accumulate一样工作,但是使用对常用操作的预定义代码代替了在每个元素中编写自定义代码。

举例来说,上面处理订单的例子可以通过使用Accumulate函数改写为如下格式:

rule "Apply 10% discount to orders over US$ 100,00"

when

$order : Order()

$total : Number( doubleValue > 100 )

from accumulate( OrderItem( order == $order, $value : value ),

sum( $value ) )

then

# apply discount to $order

end

在上面的例子中,sum是一个Accumulate函数,将所有OrderItem的$value进行累加并返回结果。

Drools 4.0 包含了下面的内建Accumulate函数:

average

min

max

count

sum

These common functions accept any expression as input. For instance, if someone wants to calculate the average profit on all items of an order, a rule could be written using the average function:

rule "Average profit"

when

$order : Order()

$profit : Number()

from accumulate( OrderItem( order == $order, $cost : cost, $price : price )

average( 1 - $cost / $price ) )

then

# average profit for $order is $profit

end

Accumulate函数都是可插入式的。这意味着如果需要,自定义的符合特定领域的函数可以很容易的加入到引擎中,并且规则可以没有任何限制的开始使用它们。要实现新的Accumulate函数,所需要做的就是建立一个实现了org.drools.base.acumulators.AccumulateFunction接口的Java类,并在配置文件或系统特性中增加一行,让引擎知道这个新的函数。作为Accumulate函数实现的例子,下面是"average"函数的实现代码:

/*

* Copyright 2007 JBoss Inc

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

* http://www.apache.org/licenses/LICENSE-2.0

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.

*

* Created on Jun 21, 2007

*/

package org.drools.base.accumulators;

/**

* An implementation of an accumulator capable of calculating average values

*

* @author etirelli

*

*/

public class AverageAccumulateFunction implements AccumulateFunction {

protected static class AverageData {

public int count = 0;

public double total = 0;

}

/* (non-Javadoc)

* @see org.drools.base.accumulators.AccumulateFunction#createContext()

*/

public Object createContext() {

return new AverageData();

}

/* (non-Javadoc)

* @see org.drools.base.accumulators.AccumulateFunction#init(java.lang.Object)

*/

public void init(Object context) throws Exception {

AverageData data = (AverageData) context;

data.count = 0;

data.total = 0;

}

/* (non-Javadoc)

* @see org.drools.base.accumulators.AccumulateFunction#accumulate(java.lang.Object, java.lang.Object)

*/

public void accumulate(Object context,

Object value) {

AverageData data = (AverageData) context;

data.count++;

data.total += ((Number) value).doubleValue();

}

/* (non-Javadoc)

* @see org.drools.base.accumulators.AccumulateFunction#reverse(java.lang.Object, java.lang.Object)

*/

public void reverse(Object context,

Object value) throws Exception {

AverageData data = (AverageData) context;

data.count--;

data.total -= ((Number) value).doubleValue();

}

/* (non-Javadoc)

* @see org.drools.base.accumulators.AccumulateFunction#getResult(java.lang.Object)

*/

public Object getResult(Object context) throws Exception {

AverageData data = (AverageData) context;

return new Double( data.count == 0 ? 0 : data.total / data.count );

}

/* (non-Javadoc)

* @see org.drools.base.accumulators.AccumulateFunction#supportsReverse()

*/

public boolean supportsReverse() {

return true;

}

}

函数的代码非常简单,并如我们所期望的,所有讨厌的集成工作都由引擎完成。最后为了将函数插入引擎,我们在配置文件中如下定义:

drools.accumulate.function.average = org.drools.base.accumulators.AverageAccumulateFunction

这里"drools.accumulate.function."作为前缀必须使用,"average"是表明函数如何在规则中使用,"org.drools.base.accumulators.AverageAccumulateFunction"是实现函数的类的完整名称。

6.5.3. The Right Hand Side (then)

RHS是对于规则的推论或操作部分的简称;这部分应当包含一系列需要执行的操作。在RHS中使用命令式或条件式代码是不好的习惯,因为规则应当是原子态的——“什么时候这样,然后就做这些”,不是“什么时候这样,可能做这些”。规则的RHS部分应该保持简短的,这保持它是声明性和可读性的。如果你发现你需要在RHS中使用命令式或and/or条件代码,那你可能需要将规则拆分为多个规则。RHS的主要目的是插入,删除修改working memory数据。这里有一些方法帮助你用来修改working memory;不需要首先引用working memory实例。

"update(object, handle);" 将告诉引擎对象已经改变(已经被绑定到LHS中的那一个),并且规则需要重新检查。

"insert(new Something());" 将在working memory中放置一个你新建的对象。

"insertLogical(new Something());" 与insert类似,但是当没有更多的fact支持当前激发规则的真值状态时,对象自动删除。

"retract(handle);" removes an object from working memory.

这些帮助方法基本上是提供使用KnowldgeHelper实例快捷方式的宏映射(查阅KnowledgeHelper接口获得更多高级操作)。KnowledgeHelper接口在RHS代码块中可以通过一个名称是drools的变量访问。如果你为插入引擎的java bean对象提供属性变更监听器,那么当对象改变时可以不用调用"update"。

6.5.4. 对自动封箱/拆箱以及元数据类型的注解

Drools尝试保持数值在它们的元数据状态或对象封装器格式,因此当在代码块或表达式中使用绑定到一个int元数据的变量时,不再需要手工拆箱;不像Drool3.0中,所有元数据自动封箱,但需要手工拆箱。绑定到对象封装器的变量仍然保持为对象;现在的jdk1.5和jdk5规则自动在这种情况下进行封箱/拆箱处理。当进行字段约束的求值时,系统尝试强制将值变为可比较的格式,因此元数据可以与对象封装器进行比较。

6.6. Query

Figure 6.42. query

查询中仅仅包含规则LHS部分的结构(不用指定when或then)。它提供了查询working memory 中符合约束条件的对象的一个简单办法。

要获得查询结果,使用WorkingMemory.getQueryResults("name"),”name”就是指query的名称。查询名称对于RuleBase来说是全局性的,因此在为一个RuleBase服务的不同Package中不要使用重复的查询名称。

下例为所有30岁以上的人建立了一个简单的查询。

Example 6.44. Query People over the age of 30

query "people over the age of 30"

person : Person( age > 30 )

end

通过在返回的查询结果(QueryResults)上进行标准的for循环遍历,每一行将返回一个QueryResult,该对象可以用来存取组元中的每一个Column。这些Column可以通过声明的名称或索引位置存取。

Example 6.45. Query People over the age of 30

QueryResults results = workingMemory.getQueryResults( "people over the age of 30" );

System.out.println( "we have " + results.size() + " people over the age of 30" );

System.out.println( "These people are are over 30:" );

for ( Iterator it = results.iterator; it.hasNext(); ) {

QueryResult result = ( QueryResult ) it.next();

Person person = ( Person ) result.get( "person" );

System.out.println( person.getName() + "\n" );

}

6.7. Domain Specific Languages 领域特定语言

如之前介绍,DSL是将规则语言扩展到你自己的问题领域中的办法。DSL将规则语言制于你的控制之下,并且可以使用所有规则语言和引擎的特性。

6.7.1. 何时使用DSL

DSL可以用作分离规则编辑(规则的作者)和在规则引擎中操作的对象的一个中间层。DSL也可以作为条件或行为的模板,使你可以在规则中重复使用,有时候可能仅仅是用来改变参数。如果你需要规则能够被非技术人员阅读和使用,如业务分析师,DSL正好合适。如果你的规则的条件或推论遵循相似的模式,可以用模板的方式表达;当你希望隐藏实现的细节,而将焦点放在业务规则上时;或者你希望通过提供预定义的模版来控制规则的修改;这些都适合使用DSL。

DSL不关注规则的运行时,它们仅仅是分解和编译时需要的特性。

注意Drools3的DSL与Drools 2基于XML的DSL很不相同。XML风格也仍然被支持的,如果你需要的话,可以查看Drools3的Xml规则语言,并考虑使用XSLT将Drools2中的XML映射为Drools3 XML语言。

6.7.2. 编辑与管理DSL

DSL配置是保存在无格式文本文件中。如果你使用IDE,你可以获得一个友好的图形编辑器(以及一些校验),但保存文件的格式十分简单。

Example 6.46. Example mapping

[when]This is {something}=Something(something=={something})

参考上面的示例,[when]指明了表达式所使用的范围,是属于LHS或RHS。在[when]之后是要用在规则之内的表达式(通常是自然语言表达式,但不是必需的)。在‘=’符号右边的部分是将要映射到的规则语言(当然这个结构依赖于你要用在LHS还是RHS上,如果是LHS,则它是LSH的语法,如果是RHS,则它是java代码的片断)。

分解器将获得你指定的表达式,然后取出输入中的{something}(称为Tokens)值进行匹配。与tokens匹配的值获得后将替换等式右边的同名处,所得到的目标表达式是规则引擎实际使用的。

要特别注意的是DSL表达式一次处理一行。这意味着在上面的例子中,所有在”There is”后面直到行未的文本将会被认为是{something}的值,作为替换目标的实际值。这样的效果可能不是当你想将不同的DSL扩展链接在一起产生一个目标表达式时想要的。作为绕过这个规则的最好方法是确认[tokens]使用字符或标点括上。这意味着分析器沿着句子检查,并将字符之间(在这里是双引号)的值取出。注意围绕的字符(双引号)在进行内部替代时不会被包含进去,只有中间的内容被使用(而随后到行尾的内容,将被视为Case中的其它情况)。[译者注:这里的内容直译不易理解,最好参看后面的例子3.30理解;]

作为一个惯例,对文本数据使用引号是规则编辑器所默认的。你也可以在{tokens}外加上引号以确认你想捕捉的数据。

Example 6.47. Example with quotes

[when]This is "{something}" and "{another}"=Something(something=="{something}", another=="{another}")

[when]This is {also} valid=Another(something=="{also}")

在DSL表达式中在可能的情况下除了使用引号以外应当尽量避免使用标点符号, 这样可以使表达式看起来简单易懂。当你刚开始定义规则时,使用DSL可能导致调式困难,但是它可以使维护变得简单,当然可读性也提高了。

"{" 和 "}" 只能用在表达式的左边以用来标记tokens。在表达式右边需要的情况下"{" 和"}"可以代表本来的意思,不作为标记看待,如:

if (foo) {

doSomething(); }

不要忘记,如果你从用户处捕捉字符串,你也需要在表达式右边的映射出加上引号,就像一个普通的规则一样,因为作为映射的结果在规则语言中也必须是有效的。

Example 6.48. Some more examples

#This is a comment to be ignored.

[when]There is a Person with name of "{name}"=Person(name=="{name}")

[when]Person is at least {age} years old and lives in "{location}"=Person(age > {age}, location=="{location}")

[then]Log "{message}"=System.out.println("{message}");

[when]And = and

上面定义的翻译规则,将下面的输入翻译成指定的结果

Example 6.49. Some examples as processed

There is a Person with name of "kitty" ---> Person(name="kitty")

Person is at least 42 years old and lives in "atlanta" ---> Person(age > 42, location="atlanta")

Log "boo" ---> System.out.println("boo");

There is a Person with name of "bob" and Person is at least 30 years old and lives in "atlanta"

---> Person(name="kitty") and Person(age > 30, location="atlanta")

6.7.3. 在规则中使用DSL

当你刚开始学习规则时,最好是按照标准的规则语言编写适合对象模型的规则。你可以使用单元测试开始。一旦你感觉习惯了,你可以提取领域语言来表达你在规则中所做的事情。注意,一旦你使用了expander关键字,如果解析器不能识别表达式,将会抛出错误,你必须将所有东西都移到DSL中。作为绕过这个限制的一个方法,你可以在每一行前面加上“>”,它将告诉解析器按照字面意思原样保留内容,不会对该行进行扩展(这在你需要调试规则问题的时候特别有用)。

当你使用构建好的DSL工作时,你会发现DSL配置起来非常快,那是因为你不断使用相同的DSL表达式新建和修改规则。这使得事情变得非常流畅。

为了使用DSL编译和运行规则,你需要将DSL配置源码与规则源码一起传入。

//source代表规则源码读取器,dsl是DSL配置读取器

PackageBuilder builder = new PackageBuilder();

builder.addPackageFromDrl( source, dsl );

你也需要在规则源文件中使用expander关键字指定dsl定义的文件名。

expander your-expander.dsl

一般会将DSL文件放在与规则文件相同的目录下,但是使用上面的代码可以从不同目录读取DSL,只需要提供对应的读取器即可。

你可以将DSL表达式合在一行上,只要解析器能够清晰的了解{tokens},否则你可能会将直到行尾的信息都读进来。DSL表达式的处理根据映射文件的顺序,按照从顶部到底部的顺序。你也可以获得跨行的规则表达式,这意味着你可以如下例所示:

Example 6.50.

There is a person called Bob who is happy

Or

There is a person called Mike who is sad

当然这里假设“Or”被映射为“or”条件元素。

6.7.4. 增加对fact的约束

当编写规则表达式时,通常要对fact声明增加许多约束。Fact可能有许多Field,所有这些Field可能在不同的时候被使用。将每一种组合情况都用DSL表示是不可能的。

DSL工具使用一种简单的约定帮你实现上面的需求。如果你的DSL表达式使用“-”开始,它会被假设为字段约束,并被自动加入在上面提供的声明中(每行是一个字段约束)。

这很容易用一个例子来解释。让我们看一下Cheese类,它包含有字段:type, price, age, country。我们可以在普通的DRL的LHS条件中向下面这样表达:

Cheese(age < 5, price== 20, type=="stilton", country=="ch")

如果你之前确认你在任何时候都需要用到上面所有的属性,那很容易使用DSL对上面的规则LHS进行编码。但问题是Cheese中可能有许多属性以及在约束时会有许多种组合方式。如果在这一案例中,你可以如下映射:

[when]There is a Cheese with=Cheese()

[when]- age is less than {age}=age<{age}

[when]- type is '{type}'=type=='{type}'

[when]- country equal to '{country}'=country=='{country}'

注意: 在"-" 和约束映射之间必须有一个空格

接下来你可以如下描述规则:

There is a Cheese with

- age is less than 42

- type is 'stilton'

解析器将挑出带有“-”的行(它们必须在自己行上),并且将它们作为一个约束加入上面的声明中。因此在本范例中,使用上面的映射后的效果等于DRL的如下描述:

Cheese(age<42, type=='stilton')

解析器将为你做所有工作,意味着你只需要为个别的约束定义映射,并且能够按照你喜欢的来组合它们(如果你正使用上下文助手,如果你在CTRL+space后按下“-”,它将方便的提供给你一串符合字段约束的对象列表。)。

6.7.5. DSL如何工作

当规则被解析后,DSL被除去。DSL配置被解析器读取,因此解析器可以扩展DSL表达式到真正的规则语言表达式。

当解析器正在处理规则时,它将检查是否用“expander”关键字声明了DSL,如果有则尝试使用DSL来翻译规则文件中的内容。如果规则文件中有表达式不能被翻译,一个错误会被加入结果中,以及错误的行号记录(该行号是使用DSL编辑规则时的行号)。目前,DSL扩展是对空格敏感的,但是将来会更加的宽松一些,包括允许使用更多的标点符号。

对规则的扩展工作本身就是为每一行在DSL配置表达式中找到匹配的项。Token占位符所对应的值被保存在以token名称为基础的map中,然后用来对目标进行值替换。这个匹配token占位符的值或者通过搜索直到行尾,或者在token占位符后面的字符被匹配。"{" 和 "}"不包括在被提取的值中,它们只是用来为tokens划分界限,你不能将"{" 和 "}"在DSL表达式中当其它字符使用,但是可以在目标中使用。

如果需要了解更深入的信息,查阅ExpanderResolver, Expander 和DefaultExpander类。因为解析器通过Expander 和ExpanderResolver 接口完成工作,因此在需要的情况下可以用你自己的扩展器作为插件取代默认的扩展器。

6.7.6. 从头开始建立DSL

如果规则是众所周知的,DSL可以帮助捕捉规则,而不用使用任何技术格式。除非像黑客帝国那样,在人们的脖子上有插孔,否则指导雇员了解计算机技术是有风险的。

规则引擎需要在对象或数据模型上操作,在许多情况下你可以预先知道这些。但是某些情况中,模型需要由规则来发现。在任何情况下,规则可以很好的与扁平对象模型一起工作。在某些情况下,这可能意味着有一个规则对象模型是主应用程序模型的子集(或从其映射)。对象模型中通常有复杂的关联和继承关系,对于规则你会希望模型尽可能简单和扁平化,并且让规则引擎推断关联性(这提供了未来的灵活性)。在之前描述,DSL可以在对象模型和规则语言之间提供一个隔绝层。

技术人员和领域专家通过使用DSL能够达到相互沟通的目的。从历史上来说,这是一个称之为“知识工程师”的角色,他是同时具有规则引擎知识,并且能够捕捉规则的人。经过一段时间,你的DSL应该可以稳定下来,这意味着所有对规则的改变都通过DSL进行。如果你是从头开始的,建议按照下面的流程安排工作:

捕获规则为松散的“if then”声明,这主要考虑到尺寸和复杂度(可能在一个文本文档中)

在捕获的规则中寻找重复的声明。也同时寻找规则对象/字段(将它们匹配到已经知道的对象模型)。

建立新的DSL,开始从前面的步骤中增加声明。提供“holes(洞)”给数据进行修改(因为许多声明是类似的,只是一些数据发生改变)。

使用上面的DSL,并且尝试编写规则,就像在第一二步的“if then”声明中那样。反复这一过程,直到模式清晰并且稳定下来。在这个阶段,你不用太担心下面的规则语言,仅仅是DSL。

在这个阶段你将要检查被规则需要的对象和字段,使它们与数据模型更协调。

在对象模型的基础上将DSL声明映射成规则语言。并重复这个过程。显然这一过程最好一小步一小步的来做,以确定所有的事情都在正确的轨道上。

在DSL中一项具有挑战性的内容是表达绑定变量。对于这点的提示是,使用DSL表达式来将给定的变量名绑定到对象类型上,这个对象类型不要带任何约束,之后你可以使用“-”子声明来增加对象类型约束。然后你就可以在DSL中的任何地方使用这个变量了,包括在规则的行为部分。

6.8. 规则流

Figure 6.43. Ruleflow

Drools已经支持一些功能可以定义规则执行的顺序,如salience、activation groups等等。当处理大量的规则时,管理这些规则的执行顺序会变得很复杂。规则流允许你使用一个流程图指定规则集评估的顺序。它允许你定义规则集应当按照顺序还是并发的方式评估,指定在什么条件下哪些规则集应当被评估等等。这里有两个规则流的例子:

规则流是可以由规则引擎获取的由图形描述的一系列步骤,其中顺序是重要的。规则流也处理条件分支。

要使用规则流,你使用规则属性(在GUI中可选)为每一个规则指定所属于的规则流组——然后定义规则流图(可以是流程图)描述规则考虑执行的顺序。

6.8.1. 设置规则所属的规则流组

rule 'YourRule'

ruleflow-group 'group1'

when

...

then

...

end

 

这个规则将被放置在称为"group1"的组中,连同其它定义在Package中的组一起。

6.8.2. 简单的规则流

Figure 6.44. Ruleflow

上面的规则流指定"Check Order"组必须在"Process Order"组之前完成。这意味着在你的规则中,首先将考虑被标记为"Check Order"规则流组的规则,然后才是"Process Order"组。你也可以使用salience(设置属性,但是难以维护,并且在规则中加入了隐含的时间相关性)或agenda group来实现。无论怎样,通过使用规则流,使得过程的顺序清晰,差不多就像元规则一样。

6.8.3. 如何建立规则流

首先使用IDE,当在一个项目中时,使用"control+N"调用新的向导:

Figure 6.45. Ruleflow

选择"JBoss Rules"项,并点击"RuleFlow file",将建立一个新的rf文件。

接下来你将看到图形化的规则流编辑器。你应该做的第一件事情是选择切换到’规则透视图’——这将为规则优化UI界面。然后确认你可以在eclipse窗口的下面看到"properties"面板。

Figure 6.46. Groups

点击在GUI中的组件面板中的RuleFlowGroup图标——你就可以开始画一些规则流组。点击它们以允许你修改名称。

在规则流组上点击,然后你将看到如下界面:

Figure 6.47. Group properties

你可以在这里看到你设置的名称,但是你也需要设置使用在规则中的实际组名。

下一步是将组连接在一起(如果它是一个简单的连续步骤)——你可以通过组件面板上的"create connection"图标建立这些连接。你也应该建立一个结束节点(也是从组件面板中找到),你只需要有一个就够了。

实际上,如果你正在使用规则流,你通常想要做比简单的顺序编组处理过程更复杂的事情。你可能更多的时候需要建立过程的分支模型。在这种情况下,使用组件面板中的"Split" 和"Join"项目。你可以使用连接符从起点连接到规则流组或者Split节点,并从Split再连接到规则流组或Join节点(就好像你在绘制一个简单的流程图一样)。你可以完全使用图形方式工作,直到你获得基本正确的图。

Figure 6.48. Complex ruleflow

上面的规则流是更加复杂的一个例子。这个例子是一个保险理赔处理过程规则流。描述:初始化申请理赔数据有效,规则开始执行(这些检查包括所有信息数据的完整性与一致性)。接下来描述一个"split"节点——基于规则流检查的一个条件(理赔的原因),它通过检查理赔原因是否是灾难来决定走向自结算组还是到另一个"split"节点。如果是一个灾难那么它决定是否灾难相应的规则产生效果。等等。你从这里所看到的是基于在规则流的一些条件,后续的处理步骤会非常的不同。注意所有的规则可以在一个Package中——使得维护简单。你可以从实际的规则中分离出流控制。

Figure 6.49. Split types

Split类型:当你点击在Split图形上时,你会在properties面板中看到上图。然后你可以选择类型:AND, OR 和XOR。有趣的选择是OR 和XOR:如果你选择OR,那么任何Split的"分支"都可能执行(如过程可以并发进行)。如果你选择XOR,那么只会有一条路。

如果你选择OR或者XOR,那么在行中有约束,你将看到右手边有一个按钮有"..."——点击按钮,你将看见约束编辑器。在这个编辑器中,你设置约束条件决定哪一个Split的分支被选择执行。

Figure 6.50. Edit constraints

选择你所希望的路径设置约束,然后你将看见如下约束编辑器:

Figure 6.51. Constraint editor

这是一个文本编辑器用来输入约束条件(如规则的约束部分一样)。这些约束作用在working memory中的fact上(如在上例中,检查对于理赔额是否小于250元)。当条件为真时,指定的路径将被选择执行。

6.8.4. 在你的应用程序中使用规则流

一旦你有了一个有效的规则流(你可以通过点击绿色的"tick"图标来检查有效性),你可以像drl那样将规则流加入Package中:

Reader rf = ... (rule flow reader)

packageBuilder.addRuleFlow(rf);

另一种方法,你可以将rf文件上传到BRMS中(作为规则流资源)并且它将自动被包含在被部署的包中。

要在运行时代码中激活指定的规则流,需要使用WorkingMemory接口。在你完成fact插入后使用:

workingMemory.startProcess("ID_From_your_Ruleflow_properties");

然后调用fireAllRules()。这样会通知引擎哪一个过程是有效的(当你可能有多个过程时考虑)。

6.9. XML规则语言

作为一个选择,Drools也支持使用XML数据格式来捕捉和管理你的规则。就像非XML格式的DRL,XML格式也被解析为内部的“AST”表现方式(为了更快的解析效率,使用SAX解析器)。XML格式中没有任何扩展和变化,所有XML支持的特性,在DRL中也支持。

6.9.1. 何时使用XML

有几种情况下,XML是合适的。但是我们希望那不是一个默认选择,因为XML非常的不易阅读,并且会使得规则膨胀的很厉害。

如果你想手工修改XML,使用一个好的支持Schema的编辑器将提供对XML更好的分级型视图,更利于观察(XMLSpy和Oxygen是很好的工具,但是要付费。)。

其它你希望使用XML的情况有:

有一个工具从某些输入产生规则(程序化产生规则);

从另一种规则语言变换过来;

从另一个发行XML的工具(使用XSLT,你可以很容易的对XML进行变换);

注意,对上面情况你一样也可以产生普通的规则。

另一种情况是,你可能需要将Drools应用到已经使用XML作为配置的环境中,因此你希望规则也是XML格式。你可以直接建立XML格式的规则,但注意的是你也可以从AST对象直接产生XML格式的规则。选择是多样的,主要依赖于开放的架构。

6.9.2.  XML 格式

一个完整的用于取代XSD的WSC标准(XMLSchema)已经提供,它也使用XML语言进行描述,在这里不再重复说明该规范。该语言的摘要如下:

Example 6.51. Example

<?xml version="1.0" encoding="UTF-8"?>

<package name="com.sample"

xmlns="http://drools.org/drools-4.0"

xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"

xs:schemaLocation="http://drools.org/drools-4.0 drools-4.0.xsd">

<import name="java.util.HashMap" />

<import name="org.drools.*" />

<global identifier="x" type="com.sample.X" />

<global identifier="yada" type="com.sample.Yada" />

<function return-type="void" name="myFunc">

<parameter identifier="foo" type="Bar" />

<parameter identifier="bada" type="Bing" />

<body>

System.out.println("hello world");

</body>

</function>

<rule name="simple_rule">

<rule-attribute name="salience" value="10" />

<rule-attribute name="no-loop" value="true" />

<rule-attribute name="agenda-group" value="agenda-group" />

<rule-attribute name="activation-group" value="activation-group" />

<lhs>

<pattern identifier="foo2" object-type="Bar" >

<or-constraint-connective>

<and-constraint-connective>

<field-constraint field-name="a">

<or-restriction-connective>

<and-restriction-connective>

<literal-restriction evaluator=">" value="60" />

<literal-restriction evaluator="<" value="70" />

</and-restriction-connective>

<and-restriction-connective>

<literal-restriction evaluator="<" value="50" />

<literal-restriction evaluator=">" value="55" />

</and-restriction-connective>

</or-restriction-connective>

</field-constraint>

<field-constraint field-name="a3">

<literal-restriction evaluator="==" value="black" />

</field-constraint>

</and-constraint-connective>

<and-constraint-connective>

<field-constraint field-name="a">

<literal-restriction evaluator="==" value="40" />

</field-constraint>

<field-constraint field-name="a3">

<literal-restriction evaluator="==" value="pink" />

</field-constraint>

</and-constraint-connective>

<and-constraint-connective>

<field-constraint field-name="a">

<literal-restriction evaluator="==" value="12"/>

</field-constraint>

<field-constraint field-name="a3">

<or-restriction-connective>

<literal-restriction evaluator="==" value="yellow"/>

<literal-restriction evaluator="==" value="blue" />

</or-restriction-connective>

</field-constraint>

</and-constraint-connective>

</or-constraint-connective>

</pattern>

<not>

<pattern object-type="Person">

<field-constraint field-name="likes">

<variable-restriction evaluator="==" identifier="type"/>

</field-constraint>

</pattern>

<exists>

<pattern object-type="Person">

<field-constraint field-name="likes">

<variable-restriction evaluator="==" identifier="type"/>

</field-constraint>

</pattern>

</exists>

</not>

<or-conditional-element>

<pattern identifier="foo3" object-type="Bar" >

<field-constraint field-name="a">

<or-restriction-connective>

<literal-restriction evaluator="==" value="3" />

<literal-restriction evaluator="==" value="4" />

</or-restriction-connective>

</field-constraint>

<field-constraint field-name="a3">

<literal-restriction evaluator="==" value="hello" />

</field-constraint>

<field-constraint field-name="a4">

<literal-restriction evaluator="==" value="null" />

</field-constraint>

</pattern>

<pattern identifier="foo4" object-type="Bar" >

<field-binding field-name="a" identifier="a4" />

<field-constraint field-name="a">

<literal-restriction evaluator="!=" value="4" />

<literal-restriction evaluator="!=" value="5" />

</field-constraint>

</pattern>

</or-conditional-element>

<pattern identifier="foo5" object-type="Bar" >

<field-constraint field-name="b">

<or-restriction-connective>

<return-value-restrictionevaluator="==" >a4 + 1</return-value-restriction>

<variable-restriction evaluator=">" identifier="a4" />

<qualified-identifier-restriction evaluator="==">

org.drools.Bar.BAR_ENUM_VALUE

</qualified-identifier-restriction>

</or-restriction-connective>

</field-constraint>

</pattern>

<pattern identifier="foo6" object-type="Bar" >

<field-binding field-name="a" identifier="a4" />

<field-constraint field-name="b">

<literal-restriction evaluator="==" value="6" />

</field-constraint>

</pattern>

</lhs>

<rhs>

if ( a == b ) {

assert( foo3 );

} else {

retract( foo4 );

}

System.out.println( a4 );

</rhs>

</rule>

</package>

 

Referring to the above example: Notice the key parts, the declaration for the Drools 4, schema, imports, globals, functions, and the rules. Most of the elements are self explanatory if you have some understanding of the Drools 4 features.

Imports: import the types you wish to use in the rule.

Globals: These are global objects that can be referred to in the rules.

Functions: this is a declaration of functions to be used in the rules. You have to specify return types, a unique name and parameters, in the body goes a snippet of code.

Rule: see below.

Example 6.52. Detail of rule element

<rule name="simple_rule">

<rule-attribute name="salience" value="10" />

<rule-attribute name="no-loop" value="true" />

<rule-attribute name="agenda-group" value="agenda-group" />

<rule-attribute name="activation-group" value="activation-group" />

<lhs>

<pattern identifier="cheese" object-type="Cheese">

<from>

<accumulate>

<pattern object-type="Person"></pattern>

<init>

int total = 0;

</init>

<action>

total += $cheese.getPrice();

</action>

<result>

new Integer( total ) );

</result>

</accumulate>

</from>

</pattern>

<pattern identifier="max" object-type="Number">

<from>

<accumulate>

<pattern identifier="cheese" object-type="Cheese"></pattern>

<external-function evaluator="max" expression="$price"/>

</accumulate>

</from>

</pattern>

</lhs>

<rhs>

list1.add( $cheese );

</rhs>

</rule>

 

Referring to the above rule detail:

The rule has a LHS and RHS (conditions and consequence) sections. The RHS is simple, it is just a block of semantic code that will be executed when the rule is activated. The LHS is slightly more complicated, certainly more so then past versions.

A key element of the LHS is the Pattern element. This allows you to specify a type (class) and perhaps bind a variable to an instance of that class. Nested under the pattern object are constraints and conditional elements that have to be met. The Predicate and Return Value constraints allow java expressions to be embedded.

That leaves the conditional elements, not, exists, and, or etc. They work like their DRL counterparts. Elements that are nested under and an "and" element are logically "anded" together. Likewisewith "or" (and you can nest things further). "Exists" and "Not" work around Patterns, to check for the existence or non existence of a fact meeting its constraints.

The Eval element allows the execution of a valid snippet of java code - as long as it evaluates to a boolean (do not end it with a semi-colon, as it is just a fragment) - this can include calling a function. The Eval is less efficient then then columns, as the rule engine has to evaluate it each time, but it is a "catch all" feature for when you can express what you need to do with Column constraints.

6.9.3. 遗留的Drools 2.x XML 规则格式

Drools 2.x 遗留的XML格式不再被XML解析器支持。

6.9.4. Automatic transforming between formats (XML and DRL)

Drools提供了一些工具类用来在格式之间提供转换。这个工作首先通过将规则从源码状态转换到AST状态,然后倒出到需要的目标格式。这允许你使用DRL编写规则,然后在需要的时候导出成XML格式。

完成所需工作的类如下:

XmlDumper - for exporting XML.

DrlDumper - for exporting DRL.

DrlParser - reading DRL.

XmlPackageReader - reading XML.

使用上面的组合,你可以在格式之间任意转换,包括转换回来。注意使用DSL的DRL在转换后不能保持应用DSL的状态,但可以进行转换。

通过使用XSLT为XML提供各种排序的可能,XSLT和它的家族让XML更强大。

第七章:部署和测试

7.1. 部署选项

一旦将规则集成在你的应用中,你需要激活如何将规则与你的应用一起部署。规则的特点是用来允许应用程序的改变而不用重新部署整个应用。这意味着规则必须作为应用的数据提供,而不是应用的部分(如嵌入classpath中)。

The recommended way of deploying depends on how you are managing your rules. If you are using the BRMS, you should use the RuleAgent (the detailed documentation for this is in the chapter on the BRMS). You can still use the RuleAgent even without the BRMS, in which case you will need to use ant (or similar) to created serialized "Package" objects of your rules.

因为每个组织都有不同,因此需要不同的部署模式。许多组织对于产品线的更改有配置管理流程。从这方面考虑,最好将规则考虑为数据而不是软件。但是,因为规则可以包含相当多的强大的逻辑,应当使用正确的测试、确认流程来控制规则的改变,在将规则发布出去之前要确认它们正确。

7.1.1. 使用RuleAgent部署

最简单以及最自动化的部署规则的方法是使用RuleAgent(规则代理)。这将在BRMS用户指南中详细描述。简单来说,规则代理需要在应用程序的外面建立规则的二进制包。

要达到上面所说的目的,你的应用程序只需要包含drools-core.jar文件——没有其它需要的依赖库(当然你也要提供规则中使用到的对象模型库)。它也意味着代理可以配置为自动监控规则变更——直接到BRMS或者从一个文件/目录。

要在你的应用中使用规则代理,使用下面代码:

RuleAgent agent = RuleAgent.newRuleAgent("/MyRules.properties");

RuleBase rb = agent.getRuleBase();

rb.newStatefulSession....

//now assert your facts into the session and away you go !

yRules.properties是位于classpath根目录下的配置文件(就上例说来)。

##

## RuleAgent configuration file example

##

dir=/my/dir

url=http://some.url/here http://some.url/here

localCacheDir=/foo/bar/cache

poll=30

name=MyConfig

在上面的配置中,代理将在/my/dir中查找二进制包,并且也在指定的URL中查找。它将获得这些包中的任何更改,并将它们用到rulebase中去。

如果你正在使用BRMS,你可以使用url特性。如果包需要被手工合并到你的产品服务器中,你可以用"file"或"dir"。

7.1.2. 使用drl源码部署

在一些情况下,我们可能希望部署drl源码。这时所有drools-compiler(编译器)依赖的库都需要加入你的应用程序的classpath中。你然后可以从文件、classpath或数据库中装载drl并且编译为所需要的二进制包。其中的窍门是知道何时股则变更(这也被称为进程内部署,如下面所说)。

7.1.3. 在你的classpath中部署规则

如果你的规则不会在应用中独自变更(随新的应用版本的发布而变更),你可以将包放入classpath。包可以是源码(这种情况下drl可以被编译,并且rulebase在第一次需要时进行缓存)或者预编译的包,并且只需要包含二进制包在classpath中。

时刻记住,这种方法中如果需要变更规则,你需要同时重新部署你的应用(如果应用是一个服务器应用——重新启动它)。

7.1.4. 可部署的对象RuleBase, Package等等.

在最简单的可能情况下,你将在应用程序中编译并创建一个rulebase(从drl源代码),然后缓存rulebase。这个rulebase可以在线程间共享,产生新的working memory处理过程事务(working memory在完成处理后删除)。这种方式是无状态模式。为了更新rulebase,装载一个新的rulebase,然后交换缓存中的rulebase(任何正在使用旧的rulebase的线程可以继续运行指定它们结束,rulebase才会被垃圾收集程序收集)。

可以有许多更灵巧的办法实现上面的案例,Drools规则引擎十分的动态,意味着许多组件能够被动态交换(rules,packages)甚至有working memory正在使用的时候。例如说,规则可以从一个有许多使用中的working memory的rulebase中删除,RETE网络将自动调整以删除该规则而不需要将其它fact重新设置一次。长期运行的working memory对于复杂的应用来说是很有用的,规则引擎随时增加库中的知识,用来对实际情况进行辅助决策(在这些例子中,展现了引擎的动态特性)。

7.1.4.1. DRL和PackageDescr

一种办法是将规则部署为源码格式。这种办法依赖于运行时引擎(必需包含编译器组件)去编译规则,并建立rulebase。一个类似的方法是部署PackageDescr对象,这意味着规则被预先解析好(对语法错误),但是没有被编译成二进制格式。使用PackageBuilder类完成编译。如果需要,你当然也可以使用XML格式的规则。

PackageDescr, PackageBuilder, RuleBaseLoader

7.1.4.2. Package

这种方法最具有灵活性。在这种情况下,使用PackageBuilder从DRL源码建立Package,但是它是可以被部署的二进制Package对象。多个Package可以被合并在一起。这意味着package可能包含一个单独的新规则,或对现有规则的更改;可以建立这样的package然后与在RuleBase中的现有package合并。RuleBase然后就能统治working memory新的规则的存在(因为RuleBase保存着对由它产生的working memory的弱引用)。RuleBase保存着Package的列表,并且合并到其中一个Package,你需要知道哪一个是你需要合并的Package(显然,只有在同样包名中的规则可以被合并)。

Package对象是可以序列化的,因此它们可以在网络上发送,或绑定到JNDI,Session等等。

PackageBuilder, RuleBase, org.drools.rule.Package

7.1.4.3. RuleBase

编译好的Package被加入RuleBase。RuleBase是可序列化的,因此它们能够被当作二进制部署单元。当RuleBase被整体更新时,这是一个有用的选项,对短期存在的working memory。如果存在的Working Memory需要动态改变规则,最好还是部署Package对象。也要小心rulebase因为序列化占用了更多的处理时间(可能在大量规则的情况下是一个问题)。

RuleBase, RuleBaseLoader

7.1.4.4. Serializing

事实上在Drools中所有与RuleBase有联系的对象都是可序列化的。为了让WorkingMemory可序列化,你设置的所有对象也必须可序列化。因此它通常可以进行远程部署,并且绑定规则到JNDI中,就像在容器中使用它们一样。[译者注:这句翻译的意思不太理解,提供原文 So it is always possible to deploy remotely, and "bind" rule assets to JNDI as a means of using them in a container environment.]

请注意,当使用Package Builder时,在继续发布你的规则之前,你可能希望检查hasError标记。如果存在错误,你可以从Package Builder中获得它们,好过在部署的时候才发现问题。

7.1.5. 部署模式

7.1.5.1. 进程内构建规则

在这种情况下,规则以源码方式提供到运行时系统。系统包含drools-compiler规则编译器组件构建规则。这是最简单的方式。

7.1.5.2. 进程外构建规则

在这种情况下,规则在运行时系统外被构建成二进制格式(例如在一个部署服务器中)。进程外构建的主要好处是,运行时系统可以最少的依赖运行规则所需的库(只有一个jar)。它也意味着所有编译错误可以在部署到运行系统前获得并处理。

在进程外使用PackageBuilder类,然后使用getPackage()获得Package对象。你接着可以序列化Package到一个文件(使用java标准序列化)。运行时系统只需要drools-core库引用,就可以使用RuleBaseFactory.newRuleBase().addPackage(deserialized package object)载入该文件。

7.1.5.3. 一些部署方法

这节包含一些部署的建议。当然你可以使用不同的技术去完成。

7.1.5.3.1. Pull style

这种模式是RuleAgent默认使用的模式。

在这种方法中,规则从规则库中被拉到运行系统中。规则库可以是一个简单的文件系统,或是一个数据库。拉规则的触发器可以是一个定时任务(检查变更),或一个发到运行时系统的请求(可能使用JMX接口)。这可能是最常用的方式。

7.1.5.3.2. Push style

在这种方式中,规则部署进程/规则库推规则到运行系统中(规则可能是源码或二进制格式)。当新的规则生效时,这可以给与更多的控制。

7.1.6. Web Services

一种发布规则的可能方式是将规则暴露为Web服务。有很多方法完成,但是目前最简单的一种方法是使用一个接口优先的进程:定义将在XML Schema中使用的fact类/模板,然后使用绑定技术产生这些与实际操作对应的规则的绑定对象。一种相反的可能是使用XSD/WSDL产生器产生对这些处理构建的类(操作对应的规则)的XML绑定。希望在将来的版本中,可以有一种自动的工具将规则暴露为Web服务(可能使用XSD作为fact来给规则操作)。

7.1.7. 未来的构想

Drools的将来版本将提供一个规则库组件,将直接支持以上的摸式,以及更多。

7.2. 测试

在这些年,测试驱动开发成为了主流,作为这种技术带给软件开发的价值已经被确认。在某方面来说,规则是编码(虽然在更高的级别),测试驱动开发的许多原则同样适用。

你可以在规则编写前提供测试来规定规则的行为。更进一步说,测试在规则频繁变动的环境下,比规则更重要。测试可以提供一个信心底线,规则的改变是否与系统相容,可以从测试中了解。当然,规则的改变也可能就是会引起测试的错误,这种情况下需要编写新的测试来覆盖新的规则行为。在测试驱动开发的方式中,测试需要经常运行,在一个规则驱动的环境中,这意味着每次规则改变都应该运行测试(甚至软件本身没有任何变化)。

7.2.1. 测试框架

对应程序员来说,Junit(或TestNG)是开发测试代码的常用工具,它们也可以用来测试规则。记住,规则的变更可能超过测试代码的范围,因此你应该随时将测试单元保持与规则的更新同步(在某些环境中可能是无法实现的)。当然,最好的想法是让测试关注规则的核心特性,这样就不需要随时改变。

显然,对于规则测试,其它非源代码驱动的架构在某些环境下也可以用来测试规则。下一节介绍了一个规则测试组件。

7.2.2. FIT for Rules – 一种规则测试框架

作为一个独立附件,有一个测试架构建立在FIT(集成测试架构)上。它允许规则测试套件(功能)在Word文档中捕捉,或Excel电子表格中(实际上任何可以保存为HTML的工具)。它利用一个表格层捕捉输入数据,并且通过对一个RuleSet中的规则根据给定的Fact进行判断有效性。因为测试被保存在文档中,这些方法和需求能够被保存在同样的文档中,为规则行为的提供一个单独的真实性约束。

同样因为测试文档不是代码,它们可以随着规则不断更新,用来确定变更规则的有效性。因为输入的格式对于熟悉规则的领域的人来说相当简单,它也对“情景测试”有帮助,对这些规则可以使用不同的情景。这些情景然后可以作为测试保存,增加当规则发生变更时仍然按照用户希望的工作的保证性。

这个测试框架建立在FIT和JSR-94之上,对于Jboos规则来说是一个独立的项目。因为建立在FIT上,它需要一个不同的认证,但也是开源的。你可以下载并获得更多的资料,通过这个Web页面: http://fit-for-rules.sourceforge.net/

下面的页面显示FIT规则框架的截图

使用规则FIT,你可以捕捉测试数据,将它传递到规则引擎,然后确认结果(使用测试组建文档)。期望在将来,Drools服务工具可以为测试提供一个类似的集成框架(单元格的底色,绿色意味成功,红色意味失败)。访问http://fit.c2.com获得更多关于FIT框架信息。

更多的信息可以从这里下载 Here

第八章.  BRMS (业务规则管理系统)

8.1. 简介

本节介绍BRMS。查看其它相关章节了解如何安装使用和管理。

Figure 89..1. The BRMS in action

8.1.1. 什么是BRMS?

BRMS 代表业务规则管理系统。

这是Jboss规则系统中覆盖规则管理,保存,修改和发布的组件。该组件提供基于Web的管理界面,因此能够为不在IDE或文本编辑器中工作的用户使用,但是它预计为许多用户使用。

BRMS允许用户在多用户环境下管理规则,它是你的商业规则的统一视图,允许在可控制条件下的变更,以及友好的用户界面。

8.1.1.1. 何时使用BRMS

如果有下列情况,考虑使用BRMS:你需要管理规则的版本/部署,你需要让多个用户或不同技术水平的用户访问并修改规则,你没有任何现有系统管理规则,你有许多商业规则(相反的,技术规则是应用程序的一部分)。

BRMS可以独立使用,或者与IDE工具一起使用(通常组合使用)。

BRMS可以被“打上烙印”成为你应用的一部分,或者它可以是一个中心规则库。

8.1.1.1.1. 何时不要使用BRMS

在某些情况的应用中可能将规则保存在数据库里(作为现存应用的一部分的实例),现在应用需要管理这些规则。

另外,可能现存系统已经有规则管理系统和相应用户界面(并且与你的整个环境相融合),这种情况下迁移到BRMS可能是不必要的。

如果你使用规则去解决复杂的算法问题,并且规则本质上是应用整体的一部分(并且不需要管理分离的代码部分)。

8.1.1.2. 谁使用BRMS

使用BRMS的用户的主要角色是:商业分析师,规则专家,开发者,管理员(规则管理员等)

BRMS被设计来使得这些不同的角色可以协调工作,它在安全机制下可以控制对不同用户暴露多少功能。

8.1.2. 特性概要

多种类型的规则编辑器(GUI,Text)

版本控制(历史资产)

分类别管理

构建与部署

将多种规则资产保存在一个Package中

8.2. 管理指南

这章涵盖安装和管理BRMS遇到的问题。

BRMS是一个Web应用,可以运行在多种环境中,并且被配置为适合大多数情况。这里同时也提到数据初始化设置和一些导入/导出函数。

8.2.1. 安装

对于大多数人来说安装是很简单的。BRMS应用作为一个war文件部署,它可以被部署在应用服务器或Servlet容器中,如果你接受默认值,则不需要或需要很少的配置。

当你已经下载了BRMS发布包(你可以从http://labs.jboss.com/jbossrules/downloads得到),你将发现drools-jbrms.war文件在压缩文件中。负责WAR文件到应用服务器的发布目录,然后启动你的应用服务器。如果你需要自定义一些配置,你可以首先解压war文件,修改配置然后重新压缩它,或者以解压状态部署。

一旦drools-jbrms.war已经被放置在部署目录中,并且应用服务程序启动,你可以浏览http://localhost/drools-jbrms,并检查BRMS是否出现。

一旦BRMS出现,配置完成,准备开始使用!

8.2.1.1. 支持的与被推荐的平台

BRMS可以运行在任何支持Java SE5(不需要达到JEE5)的应用服务器中,这包括像tomcat这样的servlet容器。

它在Jboss平台上被活跃的测试,并且在可以的情况下推荐使用它,或者没有任何现存的相似系统。无论怎样,通过适当的配置,它可以使用于任何容器/应用服务器(参考wiki获得更多提示)。

下面是推荐平台列表(所有都是免费的),在所有情况下,你可以使用更新的版本来代替,BRMS同样可以正常工作(因为可能这些服务器已经推出新的版本):

JBoss Application Server 4.0.5

这被推荐为一般的应用服务器方案,如果你需要在BRMS旁运行其它应用。

JBoss Web 1.0.1

如果你需要一个“更轻量级”的服务器仅仅用来运行BRMS(可能是独立的),这是一个最好的选择

你当然可以从www.jboss.com下载的这些系统。

部署到Jboss平台:如果你安装了新的Jboss平台,war可以被复制到[app server directory]/server/default/deploy。然后通过运行在[app server directory/bin]目录中的run.sh或run.bat启动服务器。

8.2.2. 数据库配置

BRMS使用JCR标准保存资产(如规则)。默认的实现是Apache Jackrabbit (http://jackrabbit.apache.org/)。BRMS包括了一个外部的存储引擎/数据库,你可以使用它,或者通过配置使用已有的RDBMS。

8.2.2.1. 改变数据存储的位置

当你第一次运行BRMS时(启动应用服务器),它将建立一个数据库在[app server directory]/bin/目录(假设你使用Jboss平台)。那有一个repository.xml文件,并且存储库目录自动建立。

数据存储库的位置应当是一个安全的位置,那是后备的。默认的位置可能不能满足这点,因此最容易的方法是设置一个更合适的位置。如果你想改变这点,请确认你已经停止了BRMS(例如停止应用服务器或卸载BRMS)。

为了改变位置,解压WAR文件,在WEB-INF目录中查找到components.xml文件。这是一个Jboss Seam配置文件(Seam是框架使用的),它允许对系统的不同部分进行自定义。当你找到components.xml文件后,你应该看到如下文本:

<component name="repositoryConfiguration">

<!--

*** This is for configuring the "home" directory for the repo storage. the directory must exist. ***

<property name="homeDirectory">/home/michael/RulesRepository_001</property>

-->

...

</component>

找到"repositoryConfiguration"节点和它的属性"homeDirectory"。

如果你取消注释状态(如上面处于注释中),你可以设置你需要的任何路径用来保存数据。你也可以使用这个将存储库四处移动。在那个例子中,当你已经设置了components.xml中的位置,你可以简单的移动repository.xml和存储目录到你设置的新位置。

如果在指定位置(或默认位置)没有存储库,BRMS会新建一个空的。

在repository.xml中有许多可以用来定义的配置,但除了上面所说的,其它大部分不推荐改变默认值。

8.2.2.2. 配置BRMS 使用扩展RDBMS

在一些情况下可能必须使用扩展的RDBMS,如Oracle,MySQL或Microsoft SQL Server作为数据存储,这是被BRMS支持的。在这些情况下,最容易的方法是使用默认的启动项,让BRMS产生默认的repository.xml基本信息。

查找到repository.xml文件并打开它,它将有注释对许多选项进行描述。从这里开始,你需要了解一些关于Jackrabbit Persistence managers管理的知识:http://jackrabbit.apache.org/doc/config.html

那里有一些persistence managers,一些是数据库规范(如Oracle)。这是一个SimpleDBPersistenceManager,与任何支持JDBC的数据一起工作,你也要指定数据库类型,这样它可以使用特定的DDL建立表结构(支持所有主要的数据库)。

BRMS在第一次启动时如果面对的是一个空的RDBMS,它将建立这些表,因此要注意用户权限必须支持可以建立这些表(至少在初始化,在第一次运行时,之后可以被锁定)。

8.2.2.3. 查询和索引,版本存储

Jackrabbit有一个独立的存储区域,对版本进行存储(旧的版本数量随时会增加,但是不会影响主数据存储的性能)。版本存储也在repository.xml有它自己的persistence manage管理配置,但对于大多数清苦,你可以使用与主存储相同的数据库(只是使用不同的对象前缀,如在你的数据库中,所有的版本数据使用“version_”作为前缀,除此之外都相同)。查看repository.xml了解更多细节。

Lucene被用来对半结构化数据提供索引,并且跨越版本。这个索引通常最好保存在文件系统中,在BRMS的局部(在repository.xml中的默认),在大多数情况下,默认是合适的。

8.2.3. 安全性

安全性使用War文件中的components.xml文件配置。要对安全进行自定义,你需要解压war文件,在WEB-INF目录中找到components.xml。

JAAS标准被用作潜在的认证和授权机制,这导致它非常的灵活,可以集成到绝大多数现存的环境中。

在盒子外,BRMS显示一个登录界面,但没有进行安全认证,用户名倍使用,但密码不执行检查。为了实现强制认证,你需要使用一个适当的用户目录来配置它(你可能已经有Active Directory或其它类似)。

在components.xml文件中,你应该找到安全配置定义节如下:

<!-- SECURITY CONFIGURATION -->

 

<!-- default (will take any username, useful if you want to keep track of users but not authenticate -->

<security:identity authenticate-method="#{defaultAuthenticator.authenticate}"/>

<!-- NO authentication. This will bypass the login screen when you hit the app. Everyone is "guest" -->

<!-- <security:identity authenticate-method="#{nilAuthenticator.authenticate}"/> -->

你可以从上面看到,这两个“开箱即用”的选项是畅通的,这意味着任何用户允许访问,在这里没有登录界面(你可以通过Web服务器从任何地方访问该页面)。

8.2.3.1. 使用你的容器的安全性和LDAP

每个应用服务器支持高级配置,可以与你现存的安全系统一起工作。Jboss AS在这里作为一个示范。

<security:identity authenticate-method="#{authenticator.authenticate}"

jaas-config-name="other"/>

这将在Jboss AS中使用“other其它”jaas配置。如果你查看[jboss install dir]/server/default/conf目录,将发现一个login-config.xml文件。这个文件包含不同的配置。如果你使用“other”像上面那个一样,那么它会在conf目录查找users.properties 和roles.properties文件,寻找用来进行认证的用户名和密码(这适合于固定的小规模的用户群)。

对于大企业,LDAP可能是最常用的选择,下面是与Active Directory一起工作的例子。你可以从下面网址获得更多关于在Jboss AS 中如何配置LDAP的信息:http://wiki.jboss.org/wiki/Wiki.jsp?page=LdapLoginModule

http://wiki.jboss.org/wiki/Wiki.jsp?page=LdapExtLoginModule

<application-policy name="brms">

<authentication>

<login-module code="org.jboss.security.auth.spi.LdapExtLoginModule" flag="required" >

<!--

Some AD configurations may require searching against

the Global Catalog on port 3268 instead of the usual

port 389. This is most likely when the AD forest

includes multiple domains.

-->

<module-option name="java.naming.provider.url">ldap://ldap.jboss.org:389</module-option>

<module-option name="bindDN">JBOSS\someadmin</module-option>

<module-option name="bindCredential">password</module-option>

<module-option name="baseCtxDN">cn=Users,dc=jboss,dc=org</module-option>

<module-option name="baseFilter">(sAMAccountName={0})</module-option>

<module-option name="rolesCtxDN">cn=Users,dc=jboss,dc=org</module-option>

<module-option name="roleFilter">(sAMAccountName={0})</module-option>

<module-option name="roleAttributeID">memberOf</module-option>

<module-option name="roleAttributeIsDN">true</module-option>

<module-option name="roleNameAttributeID">cn</module-option>

<module-option name="roleRecursion">-1</module-option>

<module-option name="searchScope">ONELEVEL_SCOPE</module-option>

</login-module>

</authentication>

</application-policy>

要使用上面的配置,你需要在BRMS的components.xml中的security:identity标记中设置jaas-config-name="brms"。

类似的配置范例可以在其它目录服务中发现。

LDAP不是最后的选择,你可以使用JDBC访问用户数据库,或者你可以编写你自己的登录模块来和任何需要处理的系统进行验证和授权集成(那是另一种情况了,但是是可行的)。查阅Jboss AS文档(或你所使用的应用服务器文档)。

8.2.4. 数据管理

8.2.4.1. 备份

如何执行备份依赖于使用什么persistence manager模式。使用默认的情况,那备份存储库目录有一个麻烦问题(不论你将它配置在哪里)。恢复也会遇到和备份一样的麻烦。

当备份系统开始工作时,最好你停下BRMS应用,或者确认没有人使用它。

在使用扩展数据库的情况下(如Oracle,MySQL),那你可以使用备份数据库中数据的方式进行BRMS数据备份。这种情况下,当恢复的时候,清除索引是一个好主意(删除索引所在目录),这样它们可以根据数据重新建立索引(这保证是同步的)。

8.2.4.2. 资产列表定制

在BRMS的一些地方有一个资产列表:这个列表能够通过AssetListTable.properties来定制。你可以设置头名称和“getter”方法,用来组装列。例如你可以增加getCreator或getExternalSource作为额外字段,如果你正使用它们。

8.2.4.3. Adding your own logos or styles to the BRMS web GUI

Everyone loves having their own logo on screen - this is to ensure that the people using the application don't forget who they work for or what product they are using for more then a nanosecond (the consequences of them forgetting are too terrible con contemplate).

To achieve, this, you can "explode" the deployment war file, and locate the JBRMS.html file.

<html>

<head>

<meta name='gwt:module' content='org.drools.brms.JBRMS'>

<link rel='stylesheet' href='JBRMS.css'>

<title>JBoss Business Rules Management System</title>

<link rel="shortcut icon" href="images/drools.gif" type="image/gif">

<link rel="icon" href="images/drools.gif" type="image/gif">

</head>

<body>

<div ><img src="images/jbossrules_hdrlogo.png" width="279" height="70" /></d

<!-- This script is the bootstrap stuff that simply must be there; it is sent down uncompressed -->

<script language='javascript' src='gwt.js'></script>

<iframe id='__gwt_historyFrame' style='width:0;height:0;border:0'></iframe>

</body>

</html>

The above is the contents of the JBRMS.html file - it is faily empty (as most of the work is done by the GWT - the GUI is built dynamically in the browser). The parts you can customise are the style sheet - you can either edit the JBRMS.css (or better yet, take a copy, and change the style to be what you need), the "shortcut icon" (its what shows in the address bar in the browser etc - also change the "icon" link to be the same so it works in IE), and the header logo. The rest should be left as is, to allow the GWT components to be loaded and attached to the page. This html page is loaded only once by the browser when the user accesses the BRMS web GUI.

The best way to customise is to take a copy of the JBRMS.html - and then edit. You can also change the URL by editing the web.xml via the normal means.

8.2.4.4. 导入和导出

从Web界面的Admin部分可以使用一个标准的JCR导入/导出特性。

这将把整个存储库导出成由JCR标准定义的XML格式。

在导入情况下,它将清除数据库中任何存在的内容。

这不是一个备份的代替品,但是当迁移时很有用。要注意的是,这种方法不会导出历史版本,只有当前的状态被导出。因此仍然推荐对于存储数据库建立一个正式的备份制度。

8.3. 体系结构

本节介绍BRMS的内核——如果你只是考虑集成或BRMS应用的最终用户,那你不需要用到它。但是Jboss Rules是开源程序,因此构建架构是手册的一部分。

如果你想重用组件或者将应用包含在你自己的程序中,那你可能想从源码构建。

Figure 89..2. Architectural diagram

上图显示系统的主要组件以及它们如何被集成和部署。管理指南部分对此有更详细的说明,系统是高度可配置的(如数据库)。

BRMS作为一个war文件部署,它通过web方式向用户提供接口,并且通过URL(或文件)提供二进制包。它利用JSR-170标准作为数据存储(JCR)标准。Jboss Seam被用作组件的框架,GWT被用作创建基于ajax的用户界面的工具。

8.3.1. 从源码构建

本节将仔细说明要构建不同的组件所需要的步骤。绝大多数情况下这些是自动进行,但是手工操作可以完整的描述过程。

8.3.1.1. Modules 模块

有两个模块:drools-repository(后台)和drools-jbrms(前台以及规则集成)。drools-jbrms模块依赖于drools-repository模块,同样其它组件也依赖于drools-repository模块。BRMS是构建Drools的一部分,当你构建Drools时也会构建BRMS。

8.3.1.2. 与Maven 2工作

Maven2被用来作为构建系统。要开始构建,你需要check out所有Jboss Rules的源码。这包括其它模块以及最顶层的库和存储目录(是构建所需要的)。因为BRMS构建是Drools构建的一部分。

首先你需要进入jboss-rules的根目录check out源码树,然后运行mvn install安装所有项目内联库的组件。如果构建被打断,你可以使用标记-Dmaven.test.skip=true阻止失败的单元测试打断构建。

当你想构建BRMS时,你可以进入drools-jbrms目录,并且运行mvn package,这将运行测试然后构建一个可发布的war文件。唯一不会做的事情是重新构建GWT前端(在下一节中查看细节)。一旦你已经有war文件(在目标目录中)你就可以去做想要做的事情了!

8.3.1.3. 与GWT工作

Web前台的GUI界面是使用GWT(google web toolkit)开发的。如果你需要在构建时改变GUI,你需要下载GWT独立库。一旦GWT下载后,你可以更改在drools-jbrms目录中的build.properties文件,来指出你在哪里安装了GWT。完成这步后,你可以使用Ant任务构建GWT组件,也可以按照你的需要运行GWT在调试/宿主模式。如果你运行构建,它将使用新编译的模块更新项目中的webapp目录(GWT不使用JSP,在运行时只有html和javascript)。

8.3.1.4. 使用Eclipse进行调试,编辑和运行

每一个项目已经含有最新的eclipse项目配置,因此你只需要将它们导入你的eclipse工作空间就可以了。这些项目使用maven产生(如果有错误或过期了,使用mvn eclipse:eclipse更新它们)。它们已经被修改为有项目依赖(意味着当调试的时候你可以单步执行代码)。

需要在eclipse中定义一些环境变量(Window->Preferences->Java->Build path->Classpath variables):M2_REPO用来指出在哪里寻找maven下载的共享依赖库。GWT_HOME指出你在哪里安装GWT。GWT_DEV必须指向平台相关的“开发”jar,它包含了你所使用的GWT版本。

怎样从eclipse中运行:通常你可以运行单元测试(这时你只需要设置M2_REPO,你甚至不需要下载GWT独立库)——或者你可以使用GWT浏览器将它运行在“宿主模式”,这对调试很有帮助(从GUI到后端,你可以单步执行代码,并且动态进行数据调整)。在drools-jbrms目录中有一个JBRMS运行文件。这允许Eclipse在调试模式运行JBRMS——打开运行对话框(Run->Run),然后从列表中选择"JBRMS"。运行后将打开一个新窗口,BRMS将处于调试模式。

使用GWT下载与调试BRMS是可选的,如果你的工作没有界面问题,可以忽略这一步。

8.3.2. 可重用组件

BRMS使用服务接口从后台功能中分离GUI——在这种情况下后台包含资源库(drools-repository和JCR)以及编译器来处理规则。

主要的接口是RepositoryService,它实现在ServiceImplementation中。GWT ajax前台与该接口交互(通过GWT使用的异步回调机制)。这里的连接配置文件是components.xml(参考Seam文档以及components.xml了解细节)。

服务接口可能被另一个组件或前台重用。

GWT用户接口可能被重用——因为是GWT那里只有一个html页面:JBRMS.html。对于熟悉GWT的用户,每一个特性都可以单独使用(如在portal中)——查看JBRMSFeature类,该类实现了它(它们理论上是独立的)。

通常BRMS用自身的war文件部署,但是理论上你可以将它与你的应用程序合并(有一些需要注意的事项)——但是它容易被保存为一个单独的war,并且使得它容易在最新发布版出来时更新到最新版本。

JBRMS.html文件能够自定义——例如变更日志或将BRMS嵌入到另一个页面。查看JBRMS.html了解详情。

8.3.3. 版本和存储库

管理指南针对数据库和文件系统更详细的编写了配置选项。

资源的不同版本连同数据被保存在数据库中。

当建立快照后,由整个包组成的复件被放入JCR数据库中的独立位置。

对于熟悉jcr和jackrabbit的用户,可以在源的*.cnd文件中查找节点类型定义。在打包好的复件中,包是一个目录,每个资源是一个文件:资源可以是文本或二进制附件。

8.3.4. 贡献

如果你有兴趣贡献自己的力量,参考wiki和项目主页。一种有用的帮助是在JIRA中登记问题或需要的特性。无论怎样,如果你在JIRA中建立一个BRMS的问题,在JIRA中选择"drools-brms"作为组件是重要的,否则我们可能会失去它。

8.4. 快速使用指南

8.4.1. 快速使用指南

本节提供对BRMS的快速遍历,但是不涉及细节的内容。这里假设你已经正确安装了存储库,并且能够访问主登录界面。

Figure 89..3. Main feature areas of BRMS

上面的截图显示BRMS的主要特性区域。

Info: 初始界面,连接到相关资源

Rules: 分类与业务用户视图

Package: 对包进行配置和管理的地方

Deployment: 管理用于部署的快照

Admin: 管理员功能 (分类, 状态标记, 导入和导出)

你也可以参考wiki:

http://wiki.jboss.org/wiki/Wiki.jsp?page=RulesRepository

这里有一些教程和用户提示(因为是wiki,因此你甚至可以发表一些你自己的提示和例子或上传文件).

8.4.1.1. 初始配置

第一次运行时需要做一些初始配置。BRMS服务第一次启动时将建立一个空的存储库,然后进行下面的步骤:

一旦部署,转到"http://<your server>/drools-jbrms/",这里将显示初始化信息界面或者是登录界面(依赖于配置)

如果它是一个新的存储库,你应该先去到"Admin"(管理)页面并选择"Manage Categories"

(你的选择会增加一些分类,分类只是用来进行分组不会与执行或其它东西相关)

规则需要fact模型(对象模型)来工作,因此接下来你将会去到Package管理特性。从这里你可以点击图标新建一个Package(给它一个不带空格的有意义的名称)。

要装载一个模型,使用包含你将使用在规则和代码中的对象模型的jar文件。当你在模型编辑界面时,你可以装载一个jar文件,从你在之前步骤中建立的列表中选择Package

现在修改你的Package配置(你刚建立的)来导入你刚刚装载的fact类型(增加导入声明),并保存变更。

这时,package已经被配置好进行使用(你一般不用经常使用这些步骤)。

(注意,你也可以导入已经存在的drl Package,它将规则作为一个独立部分保存在存储库中).

8.4.1.2. 编写规则

一旦你已经设置好至少一个分类与一个Package,你可以开始编制规则

这里有许多种规则的格式,但是从BRMS角度来说它们都是资源

你通过点击规则图标新建一条规则,然后你可以输入规则名称

你也需要为规则选择一个分类,分类提供了脱离于Package观察规则的方法(并且你可以让规则在多个分类中显示)——将它想成标签一样。

选择“Business rule (guided editor)”格式

这将打开规则建模器,它是向导编辑器,你可以使用当前package的模型来增加或修改条件和行为。另外,任何为Package设置的DSL语法模板也可以使用。

当你完成了规则编辑,你可以检查改变(保存),或者你可以校验或查看源码。

你也可以从规则编辑器中增加/删除分类和其它属性,例如文档(如果你不知道如何做,使用自然语言编写文档描述规则,然后登记(check in)它,那它之后也被当作模板)

8.4.1.3. 寻找资源

在资源导航时,你可以使用规则特性,它是由分类进行的分组,或者使用Package特性和包视图(以及规则类型)。如果你知道资源的名称或者部分名称,你也可以使用快速查找,输入规则名称,它将返回匹配输入的相关资源。

8.4.1.4. 部署

当你已经修改package中的一些规则后,你可以点击package特性,打开你所希望的package,然后构建整个package。

如果构建成功,你将能够下载一个二进制的Package文件,它能够被部署到运行时系统中

你也可以获取package的快照用于部署。这将冻结那一个时间点上的package,这样任何协作的改变不会影响到package。它也使得Package发布在一个URL上: "http://<your server>/drools-jbrms/org.drools.brms.JBRMS/packages/<packageName>/<snapshotName>" (在这里你可以使用URL并且下载在部署时需要的文件).

8.4.2. BRMS 概念

8.4.2.1. 规则是资源

因为BRMS可以管理许多不同类型的规则,它们都被分类为资源。资源是任何可以按照不同版本保存在存储库中的信息。这包括决策表,模型,DSL和更多。有时单词"rule"的真正含义只是资源(你可以对规则做的操作也可以用到其它资源类型上)。你可以将资源想象成保存在目录中的许多文件。资源被分组以便于浏览,或者打成包(package)用于发布。

8.4.2.2. 分类树

Figure 89..4. Categories

分类允许规则(资源)使用任何数量的你所定义的分类来标签(或标记)。这意味着你可以显示匹配指定分类的规则列表。规则可以属于任意数量的分类。在上图中你可以看到这能够有效地建立目录/资源浏览器视图。名称可以是随意的,由BRMS管理员定义(你也可以删除/增加新的分类——如果它们当前没有使用,那你只能删除它们)。

通常分类被定义为有意义的名称,与所包含的业务规则范围匹配(如果规则被放入多个区域,可以附加多个分类)。也可以按照规则的生命周期的不同部分来分类,例如标记为"Draft(草稿)" 或"For Review(等待复审)"。

Figure 88..5. Assets can have multiple categories

上图显示分类的编辑器/浏览视图,当你打开资源时可以看见它。在这个例子中你可以看见资源属于两个分类,使用"+"按钮增加额外的分类(使用垃圾桶图标可以删除)。这意味着当使用分类来查看资源列表时,你可以看到该资源。

在上面的例子中,第一个分类"Finance"是一个顶级分类。第二个"HR/Awards/QAS"也是单独的分类,但是它是一个嵌套的分类:分类是分等级的。这意味着有一个"HR"分类,它包含"Awards"分类(事实上HR包含更多的子分类),并且"Awards"有一个QAS的子分类。这个界面显示的是"HR/Awards/QAS"——它非常像你的磁盘上的目录结构(要注意的例外当然是规则可以在多个地方显示)。

当你打开资源进行浏览和编辑的时候,它会显示出当前所属的分类的列表,如果你进行了更改(删除或增加分类)你将需要保存这个资源——这将在历史版本库中新建一个项目。改变规则所属的分类不会影响规则的执行。

Figure 88..6. Creating categories

上图显示设置分类的管理界面,系统默认是没有任何分类的。因为分类是可以分层的,因此你选择一个希望建立子分类的父分类。从这里分类也能被删除(但只有在它们没有包含任何资源的时候)。

作为惯例,资源在同时应当不止属于1个或2个分类。万一你有大量的规则,分类是很关键的。分层结构不需要太深,但是应当合适的将你的规则/资源分类成易于管理的形式。最开始不清晰是不要紧的,你可以按照需要自由的更改分类。

8.4.2.3. 资源编辑器

Figure 89..7. The Asset editor view

上图显示"asset editor(资源编辑器)"与一些标记图块。资源编辑器是用来修改规则的地方。下面是说明不同的图块在编辑器中的作用。

A

这是编辑器窗口所在,编辑器界面实际上的格式要根据编辑的资源或规则类型确定。

B

这是文档区——无限制的文本区,对规则进行描述。推荐在编写规则前先写下对规则的描述。

C

这些是可用操作——包括保存,归档,更改状态等等。归档等价于删除资源。

D

这是资源名称以及资源所在的分类列表名称。

E

这个部分包含只读的元数据,包括何时进行变更,由谁变更。

"Modified on:" – 这是最后修改的日期。

"By:" – 谁进行了最后的修改

"Note:" – 这是资源最后一次变更时的注释(如为什么进行修改)

"Version:" – 这是一个数值,每次保存变更后加一

"Created on:" – 资源建立的日期

"Created by:" – 资源最初的编制者

"Format:" – 资源类型的简写

F

这里显示资源所属的Package(你也可以从这里改变它)

G

这里是一些可选的元数据。

H

当需要时从这里显示版本历史列表。

8.4.2.4. 规则编制

BRMS支持一系列资源格式(不断增加中)。这里描述的是关键性的资源。有一些在手册的其它部分进行了说明,这些内容就不在这里重复了。

8.4.2.4.1. 业务规则与向导编辑器

向导编辑器风格的"业务规则",也被称为BRL格式。这些规则是用向导化的GUI,它基于对象模型的信息控制并提示用户进行输入。这也可以通过使用DSL文法进行扩展。

注意: 要使用BRL向导编辑器,需要之前已经有了Package的配置

另外注意的是,在Eclipse插件中也有一个向导编辑器,本节所说的绝大部分内容都可以应用到那里。

Figure 89..8. The guided BRL editor

上图显示编辑器的工作状态。下面的描述解释上图中字符图块所代表的含义。

A:规则的不同部分。WHEN是条件,THEN是行为,"(options)"是可能影响规则的可选属性

B: 这里列出一个模式,它说明规则正在寻找"Driver"fact(字段被列在下面,在这个例子中只有age)。注意绿色的箭头,它将弹出可以加入fact声明的一个列表:你可以增加更多的字段(如location),或者你可以分配一个变量名给fact(你可以在后面用到它)。就像可以加入更多的字段到这个模式中——你也可以增加多重字段约束——如跨字段的约束(age > 42 or risk > 2)。弹出的对话框显示可选项。

C:小的"-"图标指示你可以删除一些东西——在这种情况下它将删除整个Driver fact的声明。如果是下面的一个,它将只删除age约束。

D:"+"符号允许你增加更多的模式到规则的条件或行为中,或者更多的属性。在所有情况下,提供一个弹出的可选框。对于规则的WHEN部分,你可以选择在fact上加入约束(它会提供fact的列表),或者你可以使用另一个条件元素,可能的选择有:"There is no"——意味着带有约束的fact必须不存在;"There exists"——意味着至少匹配一个(但是也只匹配一次,其它的匹配不会再触发规则);"Any of"——意味着任何模式都可以匹配;然后你可以在这些更高层的模式下增加模式。如果你只选择了一个fact(如上面所示)那么所有的模式被组合起来,因为它们都是真值("and")。

E:这里显示对age字段的约束(从左到右看),绿色的箭头允许你分配一个变量名给age字段,你可以在规则后面部分用到它。接着是约束操作的列表——这个列表依赖于数据类型改变。在之后是字段的值——值字段可以是以下之一:a)文字值(如数字,文本);b)公式——在这种情况下它是一个可计算的表达式;c)变量,这时将提供一个列表来选择值。再之后是一个水平箭头图标,这是为连接约束准备:这是对于字段有多个可以选择的值的时候,如:age < 42 or age != 39。

F:这显示规则的行为,规则包含一系列行为。在这里表示,拒绝插入新的fact,后面是拒绝的原因。你可以使用一些其它的行为:可以修改存在的fact(它将通知引擎fact发生改变),或者你可以简单的在fact上设置字段值(在这种情况下引擎不知道这个更改——因为你设置一个结果)。你也可以删除fact。在绝大多数情况下,绿色的箭头将为你列出可以加入的字段的列表,这样你可以修改字段值。你输入的值是字符串——你所输入的就是值。如果值需要计算,那么在前面增加一个"="符号——这将被认为是一个公式!

G:这是规则的可选属性。在目前,只使用了salience,它是一个表现规则优先级的数值。这也是最常用的选项。

DSL语法的参数:如果规则所在Package已经有一个dsl配置,当你增加条件或行为时,它将提供DSL语法项的列表,你可以从中选择。当选择后,将增加一行到规则——这行是用户输入的DSL特定值,然后将显示一个文本编辑框。这是一个可选项,并且那里有另一个DSL编辑器。请注意,在这个编辑框中的DSL的能力看起来要少于DSL的特性集(基本上你只可以修改DSL的when和then部分——这于3.0效果上没有不同)。

下图显示在向导编辑器中使用DSL语法的情况。

Figure 89..9. DSL in guided editor

一个更复杂的例子:

Figure 89..10. A more complex BRL example

在上例中,你可以看见它使用了混合型字符串和公式。在Person上的第二个约束是一个公式(在本例中它对Person.age进行无用的计算并检查它们的名称)——在这里age和name都是Person的字段。在第三行("age is less than ..")——它也使用了公式,虽然在这里公式计算并返回值(它用于比较)——在前面的情况,它必须返回True或者False。显然公式基本上是代码块——因此这是为有经验的用户准备的。

查看"Board"模式(横排的第二个模式):它使用了一个顶级条件元素("There is no")——这意味着模式实际上寻找fact的"non existence(不存在性)"来匹配模式。注意"Any of:"——这意味着"type"字段约束或"name"字段约束其中之一匹配即可。这里是术语多重字段约束的含义(你可以嵌套这些约束,让它们像你希望的那样复杂,依赖于你希望接下来有多少人恨你:一些建议:编写过于复杂的规则会让接下来维护它们的人发疯)。

Figure 89..11. Adding constraints

上面的对话框是你想对Person增加新的约束时弹出的。在上部的中间是简单的下拉选择:你可以直接增加一个字段(将会列出Person的字段),或者你可以增加一个"Multiple field constraint(多重字段约束)"——一种在上面说过的指定的类型。下面的Advanced options:你可以增加公式(它决定True或False——就像上面的例子: "age < (age * 2) ....")。你也可以分配一个变量名给Person(这意味着你可以在规则的行为部分访问这个变量,去设置一个值等等)。

8.4.2.4.2. DSL 规则

DSL规则是文本规则,使用语言定义资源控制如何显示。

Figure 88..12. DSL rule

DSL规则是简单的规则。如上面所示,你可以使用文本编辑器。你可以使用右边的图标提供条件和行为的列表,从中选择(或者按下Control + Space弹出同样列表)。

8.4.2.4.3. 电子表格决策表

多个规则可以被保存在电子表格中,每一行是一个规则。电子表格的细节没有在这里说明(请参考专门的章节)。

Figure 88..13. Spreadsheet decision table

要使用电子表格,你需要上传一个xls(同时能够下载当前版本,如上图)。要建立一个新的决策表,当你在运行规则向导时,你会获得建立决策表的选项(在那之后你可以上传xls文件)。

8.4.2.4.4. 规则流

规则流允许你可视化的描述执行步骤——因此不是所有规则同时被评估,而是有一个逻辑流程。规则流在本章中不说明,但是你可以使用IDE绘制规则流,然后上传rf文件到BRMS。

与电子表格类似,你上传/下载规则流文件(eclipseIDE为它们准备了图形编辑器)。规则流的细节不在这里讨论。

8.4.2.4.5. Technical rules (drl) 技术规则

Technical (drl) rules are stored as text - they can be managed in the BRMS. A DRL can either be a whole chunk of rules, or an individual rule. if its an individual rule, no package statement or imports are required (in fact, you can skip the "rule" statement altogether, just use "when" and "then" to mark the condition and action sections respectively). Normally you would use the IDE to edit raw DRL files, since it has all the advanced tooling and content assistance and debugging, however there are times when a rule may have to deal with something fairly technical. In any typical package of rules, you generally have a been for some "technical rules" - you can mix and match all the rule types together of course.

Figure 89..14. DRL technical rule

8.4.2.4.6. Functions 函数

函数是另一种资源,它们不是规则,应该只在必须的时候使用。函数编辑器是文本编辑器。

Figure 88..15. Function

提示:当你有很多类似的规则时,你可以建立规则模板,它是保存在一个非激活包中的简单规则——你然后可以分类模板,并将它们复制到需要的地方(选择一个活动Package作为目标)。

8.4.2.5. 状态管理

在BRMS中的每一个资源(Package也一样)有一个状态标记。状态标记的值在BRMS管理部分设置。你可以增加自己的状态名称。与分类类似,状态不影响执行,只是单纯的信息。不像分类,资源同时只能拥有状态的一个值。

是否使用状态是完全可选的。你可以使用它管理资源的生命周期(如果你喜欢也可以用分类来管理)。

Figure 89..16. Asset status

你可以独立的改变资源的状态(像上图一样)。它立刻产生效果,不需要单独保存。

Figure 89..17. Asset status

你可以改变整个Package中所有资源的状态——设置在Package上的状态标签,同时它也将改变所有属于这个包的资源的相应状态标签。

8.4.2.6. Package 管理

配置Package通常只要做一次,由某些有规则/模型经验的人完成。也就是说,很少人需要去配置Package,一旦完成设置,需要的话它们可以复制到各处使用。Package配置是一项技术性工作,需要一定的经验。

BRMS的所有资源都位于Package中——Package像一个目录(它也被称作名称空间)。规则资源存放在主目录。在这部分的规则需要知道fact模型和名称空间。

Figure 89..18. The package explorer

上图显示包浏览器。点击一种资源类型将看到一些匹配的列表(对于有数千个规则的Package,显示这些列表可能要几秒钟——因此分类对于发现规则是重要的)。

因此同时规则(通常也是资源)可以显示在任何数量的分类中,它们只被放在Package中。如果你将BRMS想成一个文件系统,那么每个Package是一个目录,资源保存在目录中——作为一个大的文件列表。当你建立了Package用于部署的快照后,你就将所有在快照前"目录"中的资源复制到专门为快照建立的"目录"中。

Package管理特性允许你查看Package列表,然后展开它们,显示各种资源的列表(其中有许多资源,它们中的一些被分组在一起)。

资源类型:

业务资源:这里列出所有业务规则的类型,包括决策表,业务规则等等

技术资源:这是被认为具有技术性的资源,如DRL规则,规则流

函数:在BRMS你可以定义函数,当然是可选的

DSL:领域特定语言也能够作为资源保存。如果它们存在(通常只有一种),那么它们将被用在适当的编辑器界面

模型:一个Package至少需要一个模型——提供给规则

Figure 89..19. Creating new assets

从Package浏览器你可以建立新的规则,或者新的资源。有一些资源你只能从Package浏览器新建。上图的图标将启动对应的向导。如果你将鼠标放置在图标上,将会有一个tooltip告诉你它用来做什么。

Figure 89..20. Package configuration

你需要做的最关键的一件事情是配置Package。这通常是导入规则使用的类和全局变量。一旦你做出了改变,就需要保存它,这样Package就被配置好并且准备构建。例如,你可能增加了有一个叫做"com.something.Hello"类的模型,你需要增加"import com.something.Hello"在你的Package配置中并保存更改。

Figure 89..21. Package building

最后你将构建Package。这时将会显示任何捕捉到的错误。如果构建成功,那么你可以选择建立一个快照或者部署包。你也可以查看这个Package导致的"drl"。警告:如果规则数量很大,这些操作过程可能需要一些时间。

8.4.2.6.1. 导入drl packages

也可以通过导入一个存在的drl文件来建立Package。当你选择建立一个新的Package时,你可以选择上传一个drl文件。BRMS将尝试理解那个drl文件,并为你建立一个Package。在drl中的规则将被保存为独立的资源(但仍然是drl的文本内容)。注意,要实际建立这个Package,你需要上传合适的模型(如jar库)用于校验有效性,作为单独的步骤。

8.4.2.7. 版本管理

在BRMS中资源和资源的Package都是版本化的,但是这个机制有一点不同。独立的资源的保存与源码管理系统的文件类似。但是资源的包只有在建立一个快照时才产生新的版本(这通常用于部署)。下一节讨论部署管理和快照。

Figure 89..22. Asset versions

每次你改变资源,它在版本历史中建立一个新的条目。这有点像有无限次数的undo。你可以通过版本历史的列表得到一个独立的资源,并且在那一点查看(恢复)它。

8.4.2.8. 部署管理

快照, URLS 和二进制Packages:

URLs是如何构建包并提供它的核心。BRMS通过URLs提供Package(使用规则代理下载)。URLs格式如下:http://<server>/drools-jbrms/org.drools.brms.JBRMS/package/<packageName>/<packageVersion>

<packageName>是你指定给Package的名称。<packageVersion>是快照的名称或者是"LATEST"(如果是"LATEST",那么它将是Package的最新构建版本,不是一个快照)。你可以在代理中使用这些,或者你可以粘贴链接到浏览器,然后作为一个文件下载。

查阅规则代理章节了解你可以如何在应用中使用URLs(和下载二进制文件)的细节,以及规则如何被动态更新。

Figure 89..23. Deployment snapshots

上图显示配置快照视图。在左边有一个Package列表。点击指定Package将显示Package的一系列快照。从那里你可以复制,删除或查看资源快照。每一个快照可以通过URL下载或访问。

8.4.2.9.导航与发现规则

两种主要的库浏览视图是使用上面由用户定义的分类(标签)作为大纲,以及Packge浏览器视图。

分类视图提供利于你的组织理解的方法导航规则。

Figure 89..24. Category view

上图显示使用中的分类。如果可能的话,每个分类下不要放置超过一打的规则。

另一个可选的并且技术性更强的视图是使用Package浏览器。这种显示规则的方式接近它们实际在数据库中保存的方式,同样也将规则分配到Package(名称空间)和它们的类型(格式,因为规则可以有许多不同的格式)中。

Figure 89..25. Package view

上面显示浏览的另一种方法——使用Package。

8.4.3. The business user perspective

你可以从手册中看到,一些经验和实践需要用到BRMS上。实际上任何软件系统在某种意义上需要人们有一定技术,即使它使用看上去很友好的GUI界面。之前说过,在合适的情况下BRMS可以被设置为向非技术用户提供一个合适的环境。

为这些用户使用的最合适的规则格式是向导编辑器,决策表和DSL规则。你也可以在规则向导中使用一些DSL表达式(它提供用户输入数据的格式)。

你可以使用分类为非技术用户隔离规则和资源。只有被分配了指定分类的资源才会显示在规则特性中。

BRMS初始化工作应当由开发者/技术人员来完成,他们将为所有规则设置基础。他们也可以建立所有规则可以复制的模板(它们通常放置在一个虚构的包中,有一个模板的分类)。

部署工作也不应该由非技术人员完成(如之前所提及的,这由Package特性产生)。

8.4.4. 部署: 将规则与你的应用集成

管理规则是非常有趣的事情,但是怎样才能在你的应用中使用它们呢?本节讨论使用RuleAgent部署组件,它将为你自动完成绝大部分的工作。

8.4.4.1. 规则代理

规则代理是绑定在规则引擎运行核心中的组件。要使用它,你不需要额外的组件。实际上,如果你正在使用BRMS,你的应用将只需要包含drools-core.jar在classpath中,不需要其它的依赖库。

一旦你已经在BRMS中构建你的规则成为Package,你已经准备好在应用中使用这个代理。

要使用规则代理,你将在代码中进行一个调用如下:

RuleAgent agent = RuleAgent.newRuleAgent("/MyRules.properties");

RuleBase rb = agent.getRuleBase();

rb.newStatefulSession....

//now assert your facts into the session and away you go !

注意: 对于每个使用的rulebase应当只有一个RuleAgent的实例。这意味着你应当将RuleBase保存在JNDI中(或者类似的方法)。

这里假设有一个MyRules.properties文件在你的classpath的根路径上。你也可以通过参数设置来传递一个Properties对象,参数将在接下来讨论。

下面列出MyRules.properties 的内容:

##

## RuleAgent configuration file example

##

newInstance=true

file=/foo/bar/boo.pkg /foo/bar/boo2.pkg

dir=/my/dir

url=http://some.url/here http://some.url/here

localCacheDir=/foo/bar/cache

poll=30

name=MyConfig

你可以在每个配置中仅使用键的一种类型(如仅有一个"file", "dir"等等——甚至你可以通过空格指定多个项目)。注意,对于不连续的properties文件,你可以构造一个java.utils.Properties对象,然后将它传入RuleBase方法。

对于上面的例子,这些特性中的键的含义如下:

newInstance

将这个设为true,意味着RuleBase实例每当有变化的时候就会被重建。这也意味着你需要使用agent.getRuleBase()获得更新后的rulebase(任何现存使用中的rulebase不受影响)。默认是false,意味着rulebase在适当位置更新——如你不需要保持调用getRuleBase()来确认你有了最新的规则(当规则改变时,所有StatefulSession将自动更新)。

file

这是一个由空格分隔的文件列表——每一个文件都是由BRMS导出的二进制Package。你可以有一个或多个。文件的名称不重要。每个Package必须在它自己的文件中。

dir

这与文件类似,除了使用目录来代替文件,它将选择目录中所有的文件(每个是一个Package)并将它们加入RuleBase。每个Package必须在它自己的文件中。

url

这是由空格分隔的URLs列表,指向BRMS导出Package的位置(看下面获得更多细节)。

localCacheDir

这是用来联合上面的url,如果BRMS停了(url不可访问),而运行时必须启动,它可以从这里获得最后知道的好版本的Package。

poll

这是设置检查资源是否改变的秒数间隔。

name

这是用来指定被用于日志事件的代理的名称(典型的你在系统中会有多个代理)。

下面显示BRMS的部署界面,它提供Package的URLs和下载文件。

Figure 89..26. Snapshot deployment

你可以看这个"Package URI"——它是你希望复制并粘贴到代理的properties文件中的URL,指定你所需要的Package。它提供了一个准确的版本(在这里是快照)——每一个快照有它自己的URL。如果你希望使用最新的,那么使用"LATEST"代替"NewSnapshot"。

你也可以从这里下载pkg文件,如果需要的话你可以将文件放入目录中,使用RuleAgent的"file" 或 "dir"特性(这一些情况下人们不希望运行时自动链接BRMS进行更新——但是这对许多人来说是最简单的方法)。

8.4.4.2. 手工配置

这节只是对于高级用户,他们使用自己的机制集成部署。通常你使用的是规则代理。

对于这些不想使用RuleAgent的自动部署机制的人,按照你的想法来做是十分简单的。由BRMS发行的二进制Package是序列化的Package对象。你可以反序列化它们,然后加入任何rulebase——这就是你所需要做的全部工作。

在BRMS中二进制包或者是Package的最新版本(一旦你成功校验并构建一个Package)或者是部署快照。BRMS web应用通过http提供二进制包的访问URL。你也可以发出一个"HEAD"命令获得Package的最新更新时间。

8.5. 例子与教程

8.5.1. 保险经济折扣

8.5.1.1. 快速开始演示

这里通过一个例子教你使用BRMS中的关键步骤,并在一个非常简单的应用中运行规则。

下载最新的BRMS版本http://cruisecontrol.jboss.com/cc/artifacts/jboss-rules

部署BRMS war文件到Jboss4.2 AS或JbossWeb,也可以使用其它的容器。

检查你可以访问并运行BRMS。

从Drools subversion库中check out 演示项目 (这将会在以后的发布版中包括):

http://anonsvn.labs.jboss.com/labs/jbossrules/trunk/drools-examples/drools-examples-brms/

导入演示用保险业务规则库文件到BRMS,这个压缩文件可以在演示项目的files目录发现。

要做这点,打开files目录,本地解压文件,然后到BRMS的Admin->"Manage backups",选中这个文件,按下"Import"——遵循指示

在BRMS web应用中查阅规则库被怎样放置和组织,尝试建立一些规则

去到"Packages" 并构建Package

现在去到"Deployment"特性,当你点击Package时,它将显示一个快照(这是导入的一部分,如果喜欢,你可以建立更多)。

打开快照

复制显示的快照URL

定位文件 brmsdeployedrules.properties

将复制的URL放入brmsdeployedrules.properties文件

可选: 要在规则代理中使用文件或目录配置,只要依照文档更新brmsdeployedrules.properties文件

导入示例项目并执行MainClass,程序将显示下面的跟踪信息:

RuleAgent(insuranceconfig) INFO (Thu Jul 12 20:06:02 BRT 2007): Configuring with newInstance=true, secondsToRefresh=30

RuleAgent(insuranceconfig) INFO (Thu Jul 12 20:06:02 BRT 2007): Configuring package provider : URLScanner monitoring URLs: http://localhost:8080/drools-jbrms/org.drools.brms.JBRMS/package/org.acme.insurance/fmeyer With local cache dir of /Users/fernandomeyer/projects/jbossrules/drools-examples/drools-examples-brms/cache

RuleAgent(insuranceconfig) INFO (Thu Jul 12 20:06:02 BRT 2007): Applying changes to the rulebase.

RuleAgent(insuranceconfig) INFO (Thu Jul 12 20:06:02 BRT 2007): Creating a new rulebase as per settings.

RuleAgent(insuranceconfig) INFO (Thu Jul 12 20:06:02 BRT 2007): Adding package called org.acme.insurance

APPROVED: due to no objections.

APPROVED: Driver is safe and mature.

APPROVED: due to no objections.

REJECTED: Too many accidents

一旦你为想部署的规则建立了新的快照,Rule Agent(规则代理)将发现任何改变并自动部署。

第九章.  Java规则引擎API

9.1 简介

过去大部分的规则引擎开发并没有规范化,有其自有的API,这使得其与外部程序交互集成不够灵活。转而使用另外一种产品时往往意味需要重写应用程序逻辑和API调用,代价较大。规则引擎工业中标准的缺乏成为令人关注的重要方面。2003年9月定稿并于2004年8月最终发布的JSR 94(Java规则引擎API)使得Java规则引擎的实现得以标准化。

Java规则引擎API由javax.rules包定义,是访问规则引擎的标准企业级API。Java规则引擎API允许客户程序使用统一的方式和不同厂商的规则引擎产品交互,就像使用JDBC编写独立于厂商访问不同的数据库产品一样。Java规则引擎API包括创建和管理规则集合的机制,在Working Memory中添加,删除和修改对象的机制,以及初始化,重置和执行规则引擎的机制。

9.2 java规则引擎API体系结构

Java规则引擎API分为两个主要部分:运行时客户API(the Runtime client API)和规则管理API(the rules administration API)。

9.3 规则管理API

规则管理API在javax.rules.admin中定义,包括装载规则以及与规则对应的动作(执行集 execution sets)以及实例化规则引擎。规则可以从外部资源中装载,比如说URI、Input streams、XML streams和readers等等。同时,管理API提供了注册和取消注册执行集以及对执行集进行维护的机制。使用admin包定义规则有助于对客户访问运行规则进行控制管理,它通过在执行集上定义许可权使得未经授权的用户无法访问受控规则。

管理API使用类RuleServiceProvider来获得规则管理(RuleAdministrator)接口的实例。规则管理接口提供方法注册和取消注册执行集。规则管理器(RuleAdministrator)提供了本地和远程的RuleExecutionSetProvider。在前面已提及,RuleExecutionSetProvider负责创建规则执行集。规则执行集可以从如XML streams、input streams等来源中创建。这些数据来源及其内容经汇集和序列化后传送到远程的运行规则引擎的服务器上。大多数应用程序中,远程规则引擎或远程规则数据来源的情况并不多见。为了避免这些情况中的网络开销,API规定了可以从运行在同一JVM中规则库中读取数据的本地RuleExecutionSetProvider。

规则执行集接口除了拥有能够获得有关规则执行集的方法,还有能够检索在规则执行集中定义的所有规则对象。这使得客户能够知道规则集中的规则对象并且按照自己需要来使用它们。

9.4 运行时API

运行时API定义在javax.rules包中,为规则引擎用户运行规则获得结果提供了类和方法。运行时客户只能访问那些使用规则管理API注册过的规则,运行时API帮助用户获得规则对话并且在这个对话中执行规则。

运行时API提供了对厂商规则引擎API实现的类似于JDBC的访问方法。规则引擎厂商通过类RuleServiceProvider(类RuleServiceProvider提供了对具体规则引擎实现的运行时和管理API的访问)将其规则引擎实现提供给客户,并获得RuleServiceProvider唯一标识规则引擎的URL。

URL推荐标准用法是使用类似“com.mycompany.myrulesengine.rules.RuleServiceProvider”这样的Internet域名空间,这将有助于访问URL的唯一性。类RuleServiceProvider内部实现了规则管理和运行时访问所需的接口。所有的RuleServiceProvider要想被客户所访问都必须用RuleServiceProviderManager进行注册。注册方式类似于JDBC API的DriverManager和Driver。

运行时接口是运行时API的关键部分。运行时接口提供了用于创建规则会话(RuleSession)的方法,规则会话如前所述是用来运行规则的。运行时API同时也提供了访问在service provider注册过的所有规则执行集(RuleExecutionSets)。规则会话接口定义了客户使用的会话的类型,客户根据自己运行规则的方式可以选择使用有状态会话或者无状态会话。

无状态会话的工作方式就像一个无状态会话bean。客户可以发送单个输入对象或一列对象来获得输出对象。当客户需要一个与规则引擎间的专用会话时,有状态会话就很有用。输入的对象通过addObject()方法可以加入到会话当中。同一个会话当中可以加入多个对象。对话中已有对象可以通过使用updateObject()方法得到更新。只要客户与规则引擎间的会话依然存在,会话中的对象就不会丢失。

RuleExecutionSetMetaData接口提供给客户让其查找规则执行集的元数据(metadata)。元数据通过规则会话接口(RuleSession Interface)提供给用户。

使用运行时Runtime API的代码片断如下所示:

RuleServiceProvider ruleProvider = RuleServiceProviderManager.getRuleServiceProvider

("com.mycompany.myrulesengine.rules. RuleServiceProvider");

RuleRuntime ruleRuntime = ruleProvider.getRuleRuntime();

StatelessRuleSession ruleSession = (StatelessRuleSession)

ruleRuntime.createRuleSession(ruleURL, null, RuleRuntime.STTELESS_SESSION_TYPE);

List inputRules = new ArrayList();

inputRules.add(new String("Rule 1"));

inputRules.add(new Integer(1));

List resultRules = ruleSession.executeRules(inputRules);

9.5 java规则引擎API的安全问题

规则引擎API将管理API和运行时API加以分开,从而为这些包提供了较好粒度的安全控制。规则引擎API并没有提供明显的安全机制,它可以和J2EE规范中定义的标准安全API联合使用。安全可以由以下机制提供:如Java authentication and authorization service(JAAS),the Java cryptography extension(JCE),Java secure Socket Extension(JSSE),或者其它定制的安全API。JAAS能被用来定义规则执行集的许可权限,从而只有授权用户才能访问。

9.6 异常与日志

规则引擎API定义了javax.rules.RuleException作为规则引擎异常层次的根类。所有其它异常都继承于这个根类。规则引擎中定义的异常都是受控制的异常(checked exceptions),所以捕获异常的任务就交给了规则引擎。规则引擎API没有提供明确的日志机制,但是它建议将Java Logging API用于规则引擎API。

9.7 JSR小结

JSR94为规则引擎提供了公用标准API,仅仅为实现规则管理API和运行时API提供了指导规范,并没有提供规则和动作该如何定义以及该用什么语言定义规则,也没有为规则引擎如何读和评价规则提供技术性指导。JSR94规范将上述问题留给了规则引擎的厂商。

9.8 Dools API 参考

9.8.1 简介

Drools提供了一个Java规则引擎API(JSR94)的实现,它允许在一个API中支持多个规则引擎。JSR94不本身不对规则语言进行任何处理。WSC工作在Rule Interchange Format (RIF),而OMG则开始在RuleML的基础上建立一个标准,当前Haley系统已经提议一种规则语言标准称为RML。

应当记住的是,JSR94标准描述了不同规则引擎特性的“最小公分母”,这个意思是说JSR94 API中的功能要比Drools API提供的功能要少。因此在JSR94中你不能应用Drools规则引擎的全部功能。Drools需要暴露出更多的功能,像全局变量,DRL、DSL支持,Xml属性映射等等,因为JSR94提供的功能集合是非常基本的特性。更进一步说,JSR94不提供一种规则语言,你只能使用规则引擎所提供的一小部分功能,能够获得的好处很有限。因此当我们为坚持使用JSR94的程序员提供API的同时,我们强烈建议使用Drools API。

9.8.2. 如何使用

JSR9.84分为两部分工作。第一部分是管理API用来创建和注册RuleExecutionSet。第二部分是运行时Session,用来执行这些RuleExecutionSet。

9.8.2.1. 创建与注册RuleExecutionSets

RuleServiceProviderManager对注册进行管理,并返回RuleServiceProvider。Drools的RuleServiceProvider实现在类被使用Class.forName装载时自动注册通过一个静态模块注册,在大多数情况下JDBC驱动也使用同样的方式。

例6.1. 自动注册RuleServiceProvider

// RuleServiceProviderImpl is registered to "http://drools.org/" via a static initialization block

Class.forName("org.drools.jsr9.84.rules.RuleServiceProviderImpl");

// Get the rule service provider from the provider manager.

RuleServiceProvider ruleServiceProvider = RuleServiceProviderManager.getRuleServiceProvider("http://drools.org/");

RuleServiceProvider提供对RuleRuntime和RuleAdministration 的API访问。RuleAdministration提供对RuleExecutionSet进行管理的API,使用该API可以注册一个RuleExecutionSet,然后可以用RuleRuntime来返回RuleExecutionSet。

在RuleExecutionSet可以被注册前,你需要先建立它;RuleAdministrator提供工厂方法返回一个空的LocalRuleExecutionSetProvider或RuleExecutionSetProvider。LocalRuleExecutionSetProvider用来从不可以序列化的本地源中装载RuleExecutionSet,如数据流中。RuleExecutionSetProvider从可序列化的源中装载RuleExecutionSet,如DOM元素或包。"ruleAdministrator.getLocalRuleExecutionSetProvider( null );"和"ruleAdministrator.getRuleExecutionSetProvider( null );"都使用null作为参数,因为这些方法的属性映射当前不使用。

例6.2. 使用RuleAdministration API 注册 LocalRuleExecutionSet

// Get the RuleAdministration

RuleAdministration ruleAdministrator = ruleServiceProvider.getRuleAdministrator();

LocalRuleExecutionSetProvider ruleExecutionSetProvider = ruleAdministrator.getLocalRuleExecutionSetProvider( null );

// Create a Reader for the drl

URL drlUrl = new URL("http://mydomain.org/sources/myrules.drl");

Reader drlReader = new InputStreamReader( drlUrl.openStream() );

// Create the RuleExecutionSet for the drl

RuleExecutionSet ruleExecutionSet = ruleExecutionSetProvider.createRuleExecutionSet( drlReader, null );

在上例中"ruleExecutionSetProvider.createRuleExecutionSet( reader, null )"为属性映射提供了一个空参数;无论怎样,它实际上可以用来为引入的源提供配置。当Null传入时,默认装载的输入是DRL。允许为映射提供的关键字是source和dsl。Source使用drl或xml作为它的值,设置source为drl用来装载drl文件,设置为xml用来装载xml格式规则文件;xml将会忽略任何dsl键/值设置。Dsl可以获得一个读取流或一个字符串(包含dsl内容)作为值。

例6.3. 当注册LocalRuleExecutionSet 时指定DSL

// Get the RuleAdministration

RuleAdministration ruleAdministrator = ruleServiceProvider.getRuleAdministrator();

LocalRuleExecutionSetProvider ruleExecutionSetProvider = ruleAdministrator.getLocalRuleExecutionSetProvider( null );

// Create a Reader for the drl

URL drlUrl = new URL("http://mydomain.org/sources/myrules.drl");

Reader drlReader = new InputStreamReader( drlUrl.openStream() );

// Create a Reader for the dsl and a put in the properties map

URL dslUrl = new URL("http://mydomain.org/sources/myrules.dsl");

Reader dslReader = new InputStreamReader( dslUrl.openStream() );

Map properties = new HashMap();

properties.put( "source", "drl" );

properties.put( "dsl", dslReader );

// Create the RuleExecutionSet for the drl and dsl

RuleExecutionSet ruleExecutionSet = ruleExecutionSetProvider.createRuleExecutionSet( reader, properties );

当注册RuleExecutionSet时必须指定名称,用于取回它。这也是一个输入属性的字段,当前没有使用它,因此输入空即可。

例6.4. Register the RuleExecutionSet

// Register the RuleExecutionSet with the RuleAdministrator

String uri = ruleExectionSet.getName();

ruleAdministrator.registerRuleExecutionSet(uri, ruleExecutionSet, null);

9.8.2.2. 使用有状态和无状态RuleSessions

在运行时,RuleServiceProvider用来建立有状态或无状态的规则引擎Session。

例6.5. 获得RuleRuntime

RuleRuntime ruleRuntime = ruleServiceProvider.getRuleRuntime();

为了建立规则Session,你必须使用RuleRuntime的两个公共常数之一,"RuleRuntime.STATEFUL_SESSION_TYPE" 和"RuleRuntime.STATELESS_SESSION_TYPE",连同uri一起传入你希望初始化一个RuleSession的RuleExecutionSet中。属性映射可以是null,或者可以用来指定全局变量,这个在下一节说明。createRuleSession(....)方法返回一个RuleSession实例,它必须转义为StatefulRuleSession或StatelessRuleSession。

例6.6. 有状态规则:

(StatefulRuleSession) session = ruleRuntime.createRuleSession( uri,

null,

RuleRuntime.STATEFUL_SESSION_TYPE );

session.addObject( new PurchaseOrder( "lots of cheese" ) );

session.executeRules();

StatelessRuleSession有一个非常简单的API,你只能调用executeRules(List list)传入对象列表,以及一个可选的过滤器,然后结果列表被返回。

Example Stateless

(StatelessRuleSession) session = ruleRuntime.createRuleSession( uri,

null,

RuleRuntime.STATELESS_SESSION_TYPE );

List list = new ArrayList();

list.add( new PurchaseOrder( "even more cheese" ) );

List results = new ArrayList();

results = session.executeRules( list );

9.8.2.2.1. 全局变量

通过使用属性映射传入RuleSession的工厂方法,JSR9.84可以通过使用一种非轻便的方式支持全局变量。全局变量必须定义在DRL或XML文件的开始,否则会抛出异常。属性映射的键为在drl或xml声明的变量名,值是你希望在执行中使用的值。在下例中结果被放在一个作为全局变量的java.util.List的集合中。

java.util.List globalList = new java.util.ArrayList( );

java.util.Map map = new java.util.HashMap( );

map.put( "list", globalList );

//Open a stateless Session StatelessRuleSession srs = (StatelessRuleSession) runtime.createRuleSession( "SistersRules", map, RuleRuntime.STATELESS_SESSION_TYPE );

...

// Persons added to List

// call executeRules( ) giving a List of Objects as parameter

// There are rules which will put Objects in the List

// fetch the list from the map

List list = (java.util.List) map.get("list");

Do not forget to declare the global "list" in your DRL:

package SistersRules;

import org.drools.jsr9.84.rules.Person;

global java.util.List list

rule FindSisters

when

$person1 : Person ( $name1:name )

$person2 : Person ( $name2:name )

eval( $person1.hasSister($person2) )

then

list.add($person1.getName() + " and " + $person2.getName() +" are sisters");

assert( $person1.getName() + " and " + $person2.getName() +" are sisters");

end

9.8.3. 参考书目

如果你需要了解JSR 9.84的更多信息,请参考以下文章索引

Official JCP Specification for Java Rule Engine API (JSR 9.84)

http://www.jcp.org/en/jsr/detail?id=9.84

The Java Rule Engine API documentation

http://www.javarules.org/api_doc/api/index.html

The Logic From The Bottom Line: An Introduction to The Drools Project. By N. Alex Rupp, published on TheServiceSide.com in 2004

http://www.theserverside.com/articles/article.tss?l=Drools

Getting Started With the Java Rule Engine API (JSR 9.84): Toward Rule-Based Applications. By Dr. Qusay H. Mahmoud, published on Sun Developer Network in 2005

http://java.sun.com/developer/technicalArticles/J2SE/JavaRule.html

Jess and the javax.rules API. By Ernest Friedman-Hill, published on TheServerSide.com in 2003

http://www.theserverside.com/articles/article.tss?l=Jess