Drools 规则文件语法概述
概述(Overview)
以.drl为扩展名的文件,是Drools中的规则文件,规则文件的编写,遵循Drools规则语法。下面详细介绍一下Drools规则文件语法。具体参考官方文档: https://docs.jboss.org/drools/release/7.0.0.Final/drools-docs/html_single/index.html#_droolslanguagereferencechapter
DRL文件的整体结构如下:
package package-name
imports
globals
functions
queries
rules
对于上述元素,其顺序在drl文件中的顺序不重要,除了package-name
,必须是drl文件的第一个元素。上述所有的元素都是可选的,接下来我们将详细介绍上述元素。
规则(Rule)组成
一个简单的规则结构如下所示:
rule "name"
attributes
when
LHS
then
RHS
end
通常,规则文件中是不需要标点符号的。即使是规则名“name”
上的双引号也是可选的。attributes
展示规则的执行方式,也是可选的。LHS
是规则的条件部分,遵循一个固定的语法规则,在下面的内容会详细介绍。RHS
通常是一个可以本地执行的代码块。
关键字(Key Words)
从Drools 5开始,提出了“软”关键字和“硬”关键字的概念。硬关键字是保留的,不允许开发人员在编写规则文件时使用。硬关键字有如下几个:
true
false
null
软关键字是在文件内容中公认的一些字,开发人员可以在任何地方使用这些关键字,但是为了消除混淆,并不推荐开发人员在实际中使用这些关键字。软关键字有如下几个:
- lock-on-active
- date-effective
- date-expires
- no-loop
- auto-focus
- activation-group
- agenda-group
- ruleflow-group
- entry-point
- duration
- package
- import
- dialect
- salience
- enabled
- attributes
- rule
- extend
- when
- then
- template
- query
- declare
- function
- global
- eval
- not
- in
- or
- and
- exists
- forall
- accumulate
- collect
- from
- action
- reverse
- result
- end
- over
- init
注释(Comments)
注释是规则文件中一段会被规则引擎自动忽略的一段文本。drl文件中的注释采用类Java语法的方式,可以分为两类:单行注释和多行注释。
单行注释
单行注释可以简单的使用双斜杠"//"来标识。语法解析器会自动忽视其后的所有内容。样例如下:
rule "Testing Comments"
when
// this is 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
end
另外需要注意的是,“#”开头的单行注释在drl文件中已经被弃用。
多行注释
多行注释主要用于在代码块外对整个文件进行注释,以"/"开头和"/"结尾的之间所有的内容都会被语法解析器解释为注释。样例如下:
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
错误信息(Error Messages)
Drools 5 开始提出了标准的错误信息。标准化的目的是为了使开发者可以更准确更简单定位错误问题所在。接下来将介绍如正确理解和识别错误信息。
错误信息格式
标准的错误信息格式如下所示。
ErrorMessageFormat.png
错误信息包含以下几个部分:
- 1st Block: 表明当前错误的错误码。
- 2st Block: 错误可能发生的行和列。
- 3st Block: 错误信息描述。
- 4st Block: 指明错误发生的规则,函数,模板,查询等。
- 5st Block: 指明错误发生于何种模式。一般不是强制性的。
错误码描述
-
101: No viable alternative
错误码101指明了最常见的错误,语法解析器无法找到替代方案。下面有一些常见的例子:
1: rule one
2: when
3: exists Foo()
4: exits Bar() // "exits"
5: then
6: end
上述示例会产生如下错误信息:
[ERR 101] Line 4:4 no viable alternative at input 'exits' in rule one
上述例子中的exits != exists
, 解析器找不到exits
的替代方案,于是报错。下面我们可以再看一个例子。
1: package org.drools.examples;
2: rule
3: when
4: Object()
5: then
6: System.out.println("A RHS");
7: end
现在,上述的代码会产生如下错误信息:
[ERR 101] Line 3:2 no viable alternative at input 'WHEN'
这里when
是一个关键字,语法解析器在这里会遇到一个问题:rule没有文件名,而when
是一个规则的条件部分。因此报错。下面还有一个相同类型错误的示例:
1: rule simple_rule
2: when
3: Student( name == "Andy )
4: then
5: end
这里双引号缺少了另一半,因此会报错。
-
102: Mismatched input
该错误表明,语法解析器在当前输入位置中未找到一个特定的符号。下面有一些例子:
1: rule simple_rule
2: when
3: foo3 : Bar(
上述示例会产生如下错误:
[ERR 102] Line 0:-1 mismatched input '<eof>' expecting ')' in rule simple_rule in pattern Bar
要解决上述问题,需要完善上述规则语句。接下来的实例会产生多个错误:
1: package org.drools.examples;
2:
3: rule "Avoid NPE on wrong syntax"
4: when
5: not( Cheese( ( type == "stilton", price == 10 ) || ( type == "brie", price == 15 ) ) from $cheeseList )
6: then
7: System.out.println("OK");
8: end
上述代码会产生如下错误:
[ERR 102] Line 5:36 mismatched input ',' expecting ')' in rule "Avoid NPE on wrong syntax" in pattern Cheese
[ERR 101] Line 5:57 no viable alternative at input 'type' in rule "Avoid NPE on wrong syntax
该错误信息与前一个错误相关,这里只需要将```,```用```&&```替换就好了。
* **103: Failed predicate**
出现该错误信息的原因是一个验证的语义谓词被验证为false。通常这些语义谓词用于识别软关键字。以下是一个示例:
1: package nesting;
2: dialect "mvel"
3:
4: import org.drools.compiler.Person
5: import org.drools.compiler.Address
6:
7: fdsfdsfds
8:
9: rule "test something"
10: when
11: p: Person( name=="Michael" )
12: then
13: p.name = "other";
14: System.out.println(p.name);
15: end
我们可以得到如下错误信息:
* ```[ERR 103] Line 7:0 rule 'rule_key' failed predicate: {(validateIdentifierKey(DroolsSoftKeywords.RULE))}? in rule
fdsfdsfds
是个无效的关键字,语法解析器无法将其识别成一个软关键字。
-
104: Trailing semi-colon not allowed
该错误信息与eval
从句相关,当分号不是出现在其表达式的结尾时可能报此错误。可以看一下如下示例。
1: rule simple_rule
2: when
3: eval(abc();)
4: then
5: end
由于在eval
中以分号结尾,我们可以得到如下错误信息:
[ERR 104] Line 3:4 trailing semi-colon not allowed in rule simple_rule
该错误很容易修复,只要移除eval
中的分号即可。
-
105: Early Exit
drl文件中的子规则至少能被匹配选择一次,但是当子规则不能匹配任何东西的时候,就会报这个错。
以下是一个示例:
1: template test_error
2: aa s 11;
3: end
该示例会报以下错误信息:
-
[ERR 105] Line 2:2 required (…)+ loop did not match anything at input 'aa' in template test_error
-
Other Messages
开发中可能还会遇到一些其他意想不到的问题,这些问题可以向Drools的开发团队求助。
包(Package)
package
是一系列rule
或其他相关构件如imports, globals
的容器。这个成员之间相互关联,一个package
代表了一个命名空间,其中的每个rule
的名字都是唯一的,package
名字本身就是命名空间,与实际的文件和文件夹没有任何关系。常见的结构是,一个文件包含多个rule
的文件就定义成一个包。以下的线路图表明了一个包中包含的所有组成元素。
需要注意的是,一个包必须有一个命名空间,且其声明必须遵守Java命名规范。
package
语句必须出现在包的首行,其他组成部分的出现顺序无关紧要。其中package
语句结尾的分号;
是可选的。
import
Drools文件中的import
语句功能与Java中的import
语句功能类似。使用import
时,必须指定对象的全称限定路径和类型名。Drools会自动导入Java相同名字的包中的所有类,也会导入java.lang.*
。
global
global.pngglobal
用于定义全局变量。用于使应用对象对一系列规则有效。通常,用于向规则提供全局的数据和服务,特别是一些用于规则序列的应用服务,如日志、规则序列中累加的值等。全局的变量是不会插入Woking Memory
中的,另外,全局变量不要用于建立规则的条件部分,除非它是一个不会改变的常量。全部变量的改变不会通知到规则引擎,规则引擎不跟踪全局变量的变化,因为他们并没有加入到Woking Memory
中。全局变量使用不当,会产生很多不可思议的结果。如果多个包中同时定义了相同标识符的全局变量,那么这些全局变量必须是相同类型,并会引用一个相同的全局值。为了更好地使用全局变量,必须遵循以下规则:
- 在规则文件中定义常量,并使用这些常量。
global java.util.List myGlobalList;
rule "Using a global"
when
eval( true )
then
myGlobalList.add( "Hello World" );
end
- 在工作内存中,为全局变量设值。在从内存中获取所有的
fact
之前,最好将所有的全局变量设置。例如:
List list = new ArrayList();
KieSession kieSession = kiebase.newKieSession();
kieSession.setGlobal( "myGlobalList", list );
全局变量并不是用来在规则间共享数据,而且最好不要用于在规则间共享数据。规则总是对工作内存的状态产生推理和反应,因此,如果想在规则之间传递数据,可以将这些数据作为facts
传入工作内存。因为规则引擎并不会关心和跟踪这些全局变量的变化。
函数(Function)
function.pngfunction
提供了一种在规则源文件中插入语义代码的方式,与在普通Java类中不同。他们需要帮助类,否则不能做任何事情。(实际上,编译器会针对这些句子自动产生帮助类。)在规则中使用函数的最主要优点就是你可以把所有逻辑放在一个地方,你可以根据需要更改这些函数的逻辑。函数通常用于在规则的then
部分调用某些动作,特别是一些经常被用到的而传入参数不一样的动作。典型的
function
格式如下:
function String hello(String name) {
return "Hello "+name+"!";
}
需要注意的是,这里我们使用了function
关键字,即使它并不是Java语言的一部分。参数和返回值的定义和传入与Java语言一致。当然,参数和返回值并不是必须的。另外,我们可以使用一个帮助类中的静态方法,如:Foo.hello()
。
Drools支持函数的导入,我们所要做的就是:
import function my.package.Foo.hello
不需要考虑函数的定义和导入方式,我们可以直接在需要的地方直接使用函数名调用这个函数。例如:
rule "using a static function"
when
eval( true )
then
System.out.println( hello( "Bob" ) );
end
类型声明(Type Declaration)
在规则引擎中,类型声明有两个目的:允许新的类型声明;允许元数据类型的声明。
- 声明新类型:Drools工作时会将外部的普通Java对象作为事实。但是有时候使用者想要自己定义一些规则引擎可以直接使用的模型,而不用担心用Java之类的底层语言来创建对象。另外有时候,当一个域模型已经建立,但是用户想或者需要用一些主要在推理过程中使用的实体来完善这个模型。
- 声明元数据:事实往往会有一些与之相关的元数据信息。元信息的样本包含的任何种类的数据都不能代表事实的属性,且在该事实类型的所有实例中都是不变的。这些元信息在规则引擎的运行和推理古城中需要被查询。
声明新类型
为了定义新类型,我们需要使用关键字``declare,然后是一系列域,最终以
end关键字结尾。一个新的
fact必须有一系列域,否则规则引擎会去
classpath中寻找一个存在的
fact```类,如果没找到,会报错。下面给出定义新类型的几个例子。
Example . Declaring a new fact type: Address
declare Address
number : int
streetName : String
city : String
end
上面的例子中我们定义了一个新类型Address
,这个fact
有三个属性,每个属性都具有一个Java中有效的数据类型。同样的,我们可以再定义一个数据类型:
Example . Declaring a new fact type: Person
declare Person
name : String
dateOfBirth : java.util.Date
address : Address
end
我们可以看一下上面的例子,dateOfBirth
是Java中的java.util.Date
类型,address
是我们刚才声明的类型。为了不用写全称限定名,我们可以先使用import
来导入要使用的类型,例如:
Avoiding the need to use fully qualified class names by using import
import java.util.Date
declare Person
name : String
dateOfBirth : Date
address : Address
end
当我们声明一个新的fact
类型时,Drools会在编译期间生成实现自一个表示该fact
类型的Java类的字节码。这个生成的Java类
Example : generated Java class for the previous Person fact typedeclaration
public class Person implements Serializable {
private String name;
private java.util.Date dateOfBirth;
private Address address;
// empty constructor
public Person() {...}
// constructor with all fields
public Person( String name, Date dateOfBirth, Address address ) {...}
// if keys are defined, constructor with keys
public Person( ...keys... ) {...}
// getters and setters
// equals/hashCode
// toString
}
该类型生成的class是一个普通的Java类,可以在规则中直接使用,就向其他 fact
一样。见如下例子:
Using the declared types in rules
rule "Using a declared Type"
when
$p : Person( name == "Bob" )
then
// Insert Mark, who is Bob's mate.
Person mark = new Person();
mark.setName("Mark");
insert( mark );
end
声明枚举类型
DRL同时支持声明枚举类型。该类型声明需要另外一种关键字enum
,然后以都好分割可接收值的列表,最后以分号结束。
declare enum DaysOfWeek
SUN("Sunday"),MON("Monday"),TUE("Tuesday"),WED("Wednesday"),THU("Thursday"),FRI("Friday"),SAT("Saturday");
fullName : String
end
声明完成之后,该枚举类型可以用于之后的规则中。
rule "Using a declared Enum"
when
$p : Employee( dayOff == DaysOfWeek.MONDAY )
then
...
end
声明元数据
在Drools中元数据会被分配给一系列不同对象的构造:fact
类型,fact
属性和规则。Drools使用@
符号来引出元数据,使用使用如下格式:
@metadata_key( metadata_value )
其中metadata_value
是可选的。
Drools允许声明任何任意元数据属性,但是当其他属性在运行时仅仅对查询有效时,有些属性对于规则引擎来说具有不同的意义。Drools允许为fact
类型和fact
属性声明元数据。所有的元数据在该属性被分配到fact
类型前声明,而在向一个特定属性分配值之前声明。
Example 115. Declaring metadata attributes for fact types and attributes
import java.util.Date
declare Person
@author( Bob )
@dateOfCreation( 01-Feb-2009 )
name : String @key @maxLength( 30 )
dateOfBirth : Date
address : Address
end
上面的例子中,声明了两个fact
类型(@author
和 @dateOfCreation
)的元数据元素,另外为name
属性声明了两个(@key
和@maxLength
)元数据。其中@key
没有必须值,所有括号和值均被省略了。
规则(Rule)
rule.png一个规则,指定当(
when
)一系列特定的条件发生时(左手边LHS),然后(then
)做出一系列相应的动作(右手边RHS)。一个常见的问题就是:为什么使用when
而不是if
,因为if
通常是执行流程中特定时间点的一部分,是一个需要检查的条件。相反的,when
指明的是一个不绑定于任何特定判断序列或时间点的条件判断,它在规则引擎的声明周期内的任何一个条件发生的情况下都可能触发,不管这个条件是否遇到,这些动作都会执行。一个规则在一个包中必须具有独一无二的名字。如果在一个DRL文件中重复定义两个相同名字的规则,在加载的时候就会报错。如果向包中添加一个名字已经存在的规则,该规则会覆盖掉之前的同名规则。如果一个规则命中存在空格符,最好使用双引号将规则名包括起来。
规则的属性不是必须的,且属性最好写成一行。
规则中的LHS在关键字
when
的后面,同样的,RHS应该在关键字then
的后面,规则最后以关键字end
结尾。另外,规则不准嵌套。
Example . Rule Syntax Overview
rule "<name>"
<attribute>*
when
<conditional element>*
then
<action>*
end
Example . A simple rule
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
rule attributes.png
规则属性
规则属性显式地声明了对规则行为的影响,有些规则属性很简单,有些规则属性是复杂的子系统的一部分,如规则流。为了从Drools中获得更多东西,我们需要确保对每一个规则属性均有正确的认识。
常用的规则属性有如下:
-
no-loop
- 默认值:false
- type: Boolean
当规则序列更改了一个fact
,会导致该规则会被重新触发,以至于产生一个无限循环。当设置为true时,当前规则只会被激活一次。
-
ruleflow-group
- 默认值:N/A
- type: String
ruleflow
是Drools的特色之一,可以让你自己控制规则的命中。同一个ruleflow-group
中的所有规则只有当该组激活时才能被命中。
-
lock-on-active
- 默认值:false
- type: Boolean
不管何时ruleflow-group
和agenda-group
被激活,只要其中的所有规则将lock-on-active
设置为true,那么这些规则都不会再被激活,不管一开始怎么更新,这些匹配的规则都不会被激活。这是no-loop
属性的增强,因为这些变化现在不仅仅是规则自身的变化。
-
salience
- 默认值:0
- type: Integer
任何规则都有一个默认为0的salience
属性,该属性可以为0,正数和负数。salience
表示规则的优先级,值越大其在激活队列中的优先级越高。Drools支持使用动态的salience
,可以使用一个包含动态约束变量的表达式来表示。如下所示
Dynamic Salience
rule "Fire in rank order 1,2,.."
salience( -$rank )
when
Element( $rank : rank,... )
then
...
end
-
agenda-group
- 默认值:MAIN
- type: String
agenda-group
允许用户将Agenda分割成多个部分以提供更多的运行控制。
-
auto-focus
- 默认值:false
- type: Boolean
当一个规则被激活时auto-focus
为true,而且该规则的agenda-group
还没有focus,当该agenda-group
focus时,允许该规则潜在命中。
-
activation-group
- 默认值:N/A
- type: String
属于同一个activation-group的规则会进行唯一命中。也就是说同一个activation-group中的规则,只要有一个命中,其他的规则都会被取消激活状态,这样这些规则就不会被命中。
-
dialect
- 默认值:as specified by the package
- type: String
dialect用于指明规则中使用的代码的语言种类,目前支持两种语言,"java"或"mvel"。
-
date-effective
- 默认值:N/A
- type: String (包含日期和时间)
当前系统时间在date-effective之后,该规则才会被激活。
-
date-effective
- 默认值:N/A
- type: String (包含日期和时间)
当前系统时间在date-effective之后,该规则不会再被激活。
-
duration
- 默认值:无
- type: long (包含日期和时间)
duration
用于表示一个规则在一定时间之后才会被命中,如果它还是激活状态的话。
LHS语法
LHS是规则的条件部分的统称,由零到多条条件元素组成。如果LHS为空,默认为是条件部分一直为true。当一个新的WorkingMemory session创建的时候,会被激活和触发。
Example. Rule without a Conditional Element
rule "no CEs"
when
// empty
then
... // actions (executed once)
end
// The above rule is internally rewritten as:
rule "eval(true)"
when
eval( true )
then
... // actions (executed once)
end
LHS中的条件元素基于一个或多个模式,最常用的条件元素是and
。当然如果LHS中有多个不互相连接的模式时,默认使用隐式的and
。
Implicit and
rule "2 unconnected patterns"
when
Pattern1()
Pattern2()
then
... // actions
end
// The above rule is internally rewritten as:
rule "2 and connected patterns"
when
Pattern1()
and Pattern2()
then
... // actions
end
模式
模式是最终要的条件元素,它可以隐式地匹配所有插入到WorkingMemory中的所有fact
。一个模式具有0个或多个约束条件和一个可选的模式组合。模式的结构图如下所示:
下面给出一个最简单的模式的例子
Person()
这里的类型为Person,该模式意味着将匹配WorkingMemory中的所有Person对象。该类型不需要是一个真实 fact
对象的类。模式可以指向超类甚至是接口,这样可以匹配多个不同类的facts
。例如:
Object() // matches all objects in the working memory
模式的括号中条件定义了模式在何种条件下满足。如下所示:
Person( age == 100 )
为了引用匹配的对象,可以使用一个模式绑定参数如:$p
。
Example . Pattern with a binding variable
rule ...
when
$p : Person()
then
System.out.println( "Person " + $p );
end
$符号是非强制性的,只是用于在复杂的规则中方便标识,将其与变量及域区分开来。
约束
- 什么是约束?
约束是一个返回true
或false
的表达式,如下例所示:
Person( 5 < 6 ) // just an example, as constraints like this would be useless in a real pattern
约束本质上是一个与Java表达式稍微有点不同的表达式,例如equals()
等价于==
。接下来我们深入理解一下。
- Java Beans属性获取。
任何一个bean的属性都可以被直接使用,bean属性的获取也可以使用标准的Java bean getter: getMyProperty() or isMyProperty()
。例如:
//use directly
Person( age == 50 )
// this is the same as:
Person( getAge() == 50 )
同时,Drools还支持嵌套的属性获取方式,如:
//use directly
Person( address.houseNumber == 50 )
// this is the same as:
Person( getAddress().getHouseNumber() == 50 )
当然,约束中的条件表达式是支持Java表达式的,下面几个例子都是正确的:
Person( age == 50 )
Person( age > 100 && ( age % 10 == 0 ) )
Person( Math.round( weight / ( height * height ) ) < 25.0 )
- 逗号分隔符 AND
逗号分隔约束,具有隐含的AND的含义。
// Person is at least 50 and weighs at least 80 kg
Person( age > 50, weight > 80 )
// Person is at least 50, weighs at least 80 kg and is taller than 2 meter.
Person( age > 50, weight > 80, height > 2 )
逗号运算符不能出现在复合的约束表达式中,如
// Do NOT do this: compile error
Person( ( age > 50, weight > 80 ) || height > 2 )
// Use this instead
Person( ( age > 50 && weight > 80 ) || height > 2 )
- 绑定变量
属性值可以绑定到一个变量中:
// 2 persons of the same age
Person( $firstAge : age ) // binding
Person( age == $firstAge ) // constraint expression
- 分组访问嵌套对象属性
可以先看一个例子:
Person( name == "mark", address.city == "london", address.country == "uk" )
Person( name == "mark", address.( city == "london", country == "uk") )
也就是对嵌套对象属性的访问,可以组合在一个括号里面。
- 内联强制类型转换
当处理嵌套对象时,往往需要将其转换成子类,可以通过使用#
符号来完成。如下例所示:
Person( name == "mark", address#LongAddress.country == "uk" )
在该例子中将 Address
转换成LongAddress
。如果类型转换失败,该值会被认为是false。当然,类型转换也支持全称限定名称。如下所示:
Person( name == "mark", address#org.domain.LongAddress.country == "uk" )
当然,在同一个表达式中使用多级内联转换也是可行的。如下所示:
Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )
另外,Drools同样支持instanceof操作。
Person( name == "mark", address instanceof LongAddress, address.country == "uk" )
- 特殊文字支持
除了正常的Java文字,Drools还支持以下特殊的文字:- 日期文字。