Drools

Drools 规则文件语法概述

2017-06-20  本文已影响3324人  老羊_肖恩

概述(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开始,提出了“软”关键字和“硬”关键字的概念。硬关键字是保留的,不允许开发人员在编写规则文件时使用。硬关键字有如下几个:

软关键字是在文件内容中公认的一些字,开发人员可以在任何地方使用这些关键字,但是为了消除混淆,并不推荐开发人员在实际中使用这些关键字。软关键字有如下几个:

注释(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

错误信息包含以下几个部分:

错误码描述

1: rule one
2:   when
3:     exists Foo()
4:     exits Bar()  // "exits"
5:   then
6: end

上述示例会产生如下错误信息:

上述例子中的exits != exists, 解析器找不到exits的替代方案,于是报错。下面我们可以再看一个例子。

1: package org.drools.examples;
2: rule
3:   when
4:     Object()
5:   then
6:     System.out.println("A RHS");
7: end

现在,上述的代码会产生如下错误信息:

这里when是一个关键字,语法解析器在这里会遇到一个问题:rule没有文件名,而when是一个规则的条件部分。因此报错。下面还有一个相同类型错误的示例:

1: rule simple_rule
2:   when
3:     Student( name == "Andy )
4:   then
5: end

这里双引号缺少了另一半,因此会报错。

1: rule simple_rule
2:   when
3:     foo3 : 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

上述代码会产生如下错误:


 该错误信息与前一个错误相关,这里只需要将```,```用```&&```替换就好了。

* **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是个无效的关键字,语法解析器无法将其识别成一个软关键字。

1: rule simple_rule
2:   when
3:     eval(abc();)
4:   then
5: end

由于在eval中以分号结尾,我们可以得到如下错误信息:

该错误很容易修复,只要移除eval中的分号即可。

1: template test_error
2:   aa s  11;
3: end

该示例会报以下错误信息:

包(Package)

package是一系列rule或其他相关构件如imports, globals的容器。这个成员之间相互关联,一个package代表了一个命名空间,其中的每个rule的名字都是唯一的,package名字本身就是命名空间,与实际的文件和文件夹没有任何关系。常见的结构是,一个文件包含多个rule的文件就定义成一个包。以下的线路图表明了一个包中包含的所有组成元素。

package.png
   需要注意的是,一个包必须有一个命名空间,且其声明必须遵守Java命名规范。package语句必须出现在包的首行,其他组成部分的出现顺序无关紧要。其中package语句结尾的分号;是可选的。

import

Drools文件中的import语句功能与Java中的import语句功能类似。使用import时,必须指定对象的全称限定路径和类型名。Drools会自动导入Java相同名字的包中的所有类,也会导入java.lang.*

global

global.png
   global用于定义全局变量。用于使应用对象对一系列规则有效。通常,用于向规则提供全局的数据和服务,特别是一些用于规则序列的应用服务,如日志、规则序列中累加的值等。全局的变量是不会插入Woking Memory中的,另外,全局变量不要用于建立规则的条件部分,除非它是一个不会改变的常量。全部变量的改变不会通知到规则引擎,规则引擎不跟踪全局变量的变化,因为他们并没有加入到Woking Memory中。全局变量使用不当,会产生很多不可思议的结果。如果多个包中同时定义了相同标识符的全局变量,那么这些全局变量必须是相同类型,并会引用一个相同的全局值。为了更好地使用全局变量,必须遵循以下规则:
  1. 在规则文件中定义常量,并使用这些常量。
global java.util.List myGlobalList;
rule "Using a global"
when
    eval( true )
then
    myGlobalList.add( "Hello World" );
end
  1. 在工作内存中,为全局变量设值。在从内存中获取所有的fact之前,最好将所有的全局变量设置。例如:
List list = new ArrayList();
KieSession kieSession = kiebase.newKieSession();
kieSession.setGlobal( "myGlobalList", list );

全局变量并不是用来在规则间共享数据,而且最好不要用于在规则间共享数据。规则总是对工作内存的状态产生推理和反应,因此,如果想在规则之间传递数据,可以将这些数据作为facts传入工作内存。因为规则引擎并不会关心和跟踪这些全局变量的变化。

函数(Function)

function.png
   function提供了一种在规则源文件中插入语义代码的方式,与在普通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)

在规则引擎中,类型声明有两个目的:允许新的类型声明;允许元数据类型的声明。

声明新类型

为了定义新类型,我们需要使用关键字``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中获得更多东西,我们需要确保对每一个规则属性均有正确的认识。
常用的规则属性有如下:

rule "Fire in rank order 1,2,.."
        salience( -$rank )
    when
        Element( $rank : rank,... )
    then
        ...
end

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个或多个约束条件和一个可选的模式组合。模式的结构图如下所示:

Pattern.png
下面给出一个最简单的模式的例子
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

$符号是非强制性的,只是用于在复杂的规则中方便标识,将其与变量及域区分开来。
约束

约束是一个返回truefalse的表达式,如下例所示:

Person( 5 < 6 )  // just an example, as constraints like this would be useless in a real pattern

约束本质上是一个与Java表达式稍微有点不同的表达式,例如equals()等价于==。接下来我们深入理解一下。

任何一个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的含义。

// 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" )

查询(Query)

Domain Specific Languages

上一篇下一篇

猜你喜欢

热点阅读